JVM总结
1. 内存结构
线程私有区
程序计算器
- 作用:是一块较小的内存空间,存储的是当前线程所执行的字节码文件的序号
- 特点:线程私有,不会出现内存空间溢出
虚拟机栈
虚拟机栈是管理JAVA方法执行的内存模型,每个方法执行时都会创建一个当前栈桢,在当前栈桢里面存储方法的局部变量表,操作数栈,动态链接方法,返回值,返回地址等信息,栈大小决定了方法调用的可达深度(递归多少层,嵌套调用多少层其他方法,在idea中,-Xss参数可以设置虚拟机栈的大小)
- 是线程私有的
- 局部变量表存放了编译期可知的所有基本数据类型(byte,short,int,long,float,double,boolean,char),以及对象引用
- 栈太小或者方法调用过深都将抛出StackOverFlowError异常
本地方法栈
为本地语言服务的栈,Native方法服务
线程共享区
堆内存
存放对象实例的区域,对象,数组,以及常量池(从java7开始常量池也会使用堆内存)
堆内存从GC角度可以分为:新生类(Eden区,From Survision区和To Survision区),老年代,永久代(在Java8的时候被移除了)
特点:是线程贡献,需要考虑线程安全问题,同时会产生内存溢出问题
-Xms 设置最小堆内存大小(不能小于1024K)
-Xmx:设置最大堆内存大小(不能小于1024K)
方法区
用于存储已被虚拟机加载的类信息,常量,静态变量,即时编译器编译后代码等数据
特点:
- 是一块线程共享的内存区域
- 方法区的大小决定了系统可以保存多少个类,如果系统定义了太多的类,导致方法区溢出,虚拟机同样会抛出内存溢出的错误
- 现在说的方法区一般是指元数据区(元空间Metaspace,java8的时候添加的),如果不指定大小,默认情况下,虚拟机会耗尽系统的可用内存
堆栈的区别
- 存储的东西不同,栈内存存储局部变量和方法调用,而堆内存存储对象,包括成员变量,局部变量,还有类变量
- 共享不同,栈内存是线程私有的,堆内存是所有线程共有的
- 异常错误不同:栈空间不足:java.lang.StackOverFlowError,堆空间不足:java.lang.OutOfMemoryError。
- 大小不同,栈空间小于堆空间
获取堆内存数据
java.lang.Runtime类中包含了与内存先关的方法
获取剩余空间的的字节数:Runtime.freeMemory()
获取总内存的字节数:Runtime.totalMenory()
2. 垃圾回收
垃圾回收机制
在Java中,程序员不需要显示的去释放一个对象的内存,而是由虚拟机自动去执行,在JVM中,有一个垃圾回收线程,他是低优先级的,在正常情况下是不会执行的,只有在虚拟机空闲或者在堆内存不足时才会触发执行,扫描那些被视为垃圾的对象,将他们添加到回收的集合中进行回收,也可以进行手动回收,通过System.gc(),通知GC运行
垃圾回收算法
-
标记清除法
标记出所有需要回收的对象,在标记完成后,统一回收的被标记的对象
优点:速度比较快
缺点:会产生内存碎片,碎片过多,仍会使得连续空间变少
-
标记整理
标记出所有需要回收的对象,在标记完成后统一进行整理,将存活对象进行一端移动,减少内存碎片,效率相对较低
优点:无内存碎片
缺点:效率较低
-
复制算法
开辟两份大小相等的空间,一份空间始终空着,垃圾回收时将存活对象拷贝进空闲时间
优点:无内存碎片
缺点:占用空间多
-
分代回收
根据对象的存活周期不同,将对象划分为几块,比如堆内存的新生代和老年代,然后根据各个年代的特点采用最合适的算法进行回收;
新生代对象的存活时间比较短,因此使用的是复制算法,老年代对象存活的时间比较长,因此使用的是标记清除或者标记整理
分代垃圾回收的过程
分代垃圾回收器分两个区,新生代和老年代,新生代默认占1/3,老年代占2/3
新生代使用复制算法,新生代里面又分3个区(Eden,To Survivor, From Survicior)默认占比是8:1:1,当Eden区满了之后就会触发第一次MinorGC,将Eden区和From区存活的对象复制到To Survivor区域,然后to Survivor区域和From Survivor互换,原来的To Servivor区域成为下一次的From Survivor区域,然后清空Eden和From Survivor区中的对象,From中的对象每经过一次MinorGC,他的年龄值就会加1,达到15的移动到老年代,这里使用了复制算法,老年代满了或者是超过了临界值则会触发完全垃圾回收
判断一个对象是否为垃圾
引用计数法
堆中每个对象实例都有一个引用计数,当一个对象被创建的时候,且将这个对象实例分配给一个变量,该变量计数设置为1,当任何其他变量被赋值为这个对象的引用时,计数加1,但当一个对象实例的某个引用超过了生命周期,或者被设置为一个新值时,这个对象实例的引用计数将会减1,任何引用计数为0的对象都是可以被当做垃圾收集的对象,也就是一个垃圾,引用计数法容易产生循环引用的问题,如果两个对象相互引用,那么他们的引用就会一直存在,导致一直无法回收,为了解决这个问题,下面引入了可达性分析法
可达性分析算法
可达性分析算法又叫做根搜索法,就是通过一系列的称之为“GC Roots”的对象作为起始点,从这些节点开始向下搜索,搜索走过的路径被称为(Reference Chain)影响链,当一个对象到GC Roots没有任何链相连时(即从 GC Roots节点到该节点是不可达的),则这个对象就是可以被当做垃圾收集的对象,也就是一个垃圾
可以被作为GC Roots的对象
- 虚拟机栈中引用的对象
- 方法区静态成员的引用对象
- 方法区常量的引用对象
- 本地方法栈引用的对象
引用类型
-
强引用
默认声明的就是强引用,只要强引用存在,垃圾回收器就永远不会回收该对象,哪怕内存不足时也不会去回收,所以强引用是造成Java内存泄漏的主要原因,如果想回收某个对象可以将值赋值为null,切断强引用关系
-
软引用:一些非必需但是仍有用的对象,通过SoftReference实现,在内存足够的时候,软引用对象不会回收,在内存不足时则会回收,当回收了软引用对象内存还是不足时,会抛出内存溢出异常
-
弱引用:比软引用的引用强度更低一些,通过WeakReference实现,无论内存是否足够,JVM都会被进行垃圾回收
-
虚引用:最弱的引用关系,通过PhantomReference类实现,每次垃圾回收都会被回收,主要用于跟踪对象的垃圾回收状态,虚引用的对象始终返回一个null,虚引用对象始终和引用队列一起使用,当一个对象还存在虚引用时,会被加入到引用队列中