垃圾回收机制
当对象被判为垃圾的标准,即没有被其他对象引用时,才会被收回。
判断对象为垃圾的方法
引用计数法:
- 通过判断对象的引用数量来决定对象是否可以被回收
- 每个对象实例都有一个引用计数器,被引用时+1,完成引用时-1
- 任何引用计数为0的对象都有可以被当做垃圾进行回收
优点:执行效率高,程序执行所受的影响比较小
缺点:如果存在循环引用就会导致出现内存泄漏,就比如:父对象对子对象引用,反过来子对象又对父对象引用,这样计数器就永远不可能为0。
可达性分析算法:
- 通过判断对象的引用链是否可达来决定对象是否可以被回收
算法核心:通过一系列称为"GC Roots"的对象作为起始点,从这些节点开始向下搜索,搜索走过的路径
称之为"引用链",当一个对象到GC Roots没有任何的引用链相连时(从GC Roots到这个对象不可达)时,证明此对象
是不可用的。以下图为例
可以作为GC Root的对象:
- 虚拟机栈中引用的对象
- 方法区中类静态属性引用的对象
- 方法区中常量引用的对象
- 本地方法栈中JNI(即一般说的Native方法)引用的对象
即使在可达性分析算法中不可达的对象,也并非非死不可。只是暂时处于“缓刑”的阶段,若要真正死亡还要经历至少两次的标记过程:如果对象在进行行可达性分析之后发现没有与GC Roots相连接的引用链,那它将会被第一次标记并且进行一次筛选,筛选的条件是此对象是否有必要执行finalize()方法。当对象没有覆盖finalize()方法或者finalize()方法已经被JVM调用过,虚拟机会将这两种情况都视为"没有必要执行",此时的对象才是真正"死"的对象。
如果这个对象被判定为有必要执行finalize()方法,那么这个对象将会被放置在一个叫做F-Queue的队列之中,并在
稍后由一个虚拟机自动建立的、低优先级的Finalizer线程去执行它(这里所说的执行指的是虚拟机会触发finalize()
方法)。finalize()方法是对象逃脱死亡的最后一次机会,稍后GC将对F-Queue中的对象进行第二次小规模标记,如
果对象在finalize()中成功拯救自己(只需要重新与引用链上的任何一个对象建立起关联关系即可),那在第二次标记
时它将会被移除出"即将回收"的集合;如果对象这时候还是没有逃脱,那基本上它就是真的被回收了。注意:任何一个对象的finalize()方法都只会被系统调用一次,如果对象面临下一次回收,那么它的finalize()方法不会再被执行。
引用概念扩充:
- 强引用(永久有效):垃圾收集器永远不会回收的对象
- 软引用(内存不足):是一些有用但非必须的对象。在系统即将发生内存溢出异常之前,会将这些对象进行回收范围内的二次回收,如果这次回收还没有足够的内存,才会抛出内存溢出异常。
- 弱引用:被弱引用关联的对象只能生存到下一次垃圾回收之前,当垃圾收集器开始工作时,无论当前内存是否够用,都将会被回收。
- 虚引用:最弱的一种引用关系。一个对象是否有虚引用的存在,完全不会对其生存时间构成影响,也无法通过虚引用来取得一个对象实例。为一个对象设置虚引用的唯一目的就是能在这个对象被收集器回收时收到一个系统通知。
可以合理的利用软引用与弱引用来提升JVM内存的使用性能(作为缓存使用)
好文传送门 ---> 通过软引用和弱引用提升JVM内存使用性能的方法
垃圾回收算法
标记-清除算法
- 标记:标记出所有需要回收的对象
- 清除:在标记完成后统一进行清除
缺点:
效率问题:标记与清除两个过程的效率都不高
空间问题:标记清除算法会产生大量不连续的内存空间
复制算法(Eden与Survive)新生代回收算法
- 解决碎片化问题
- 顺序分配内存,简单高效
- 适用于对象存活率低的场景,需要被复制的对象不多
缺点:在应对对象存活率较高的场景就不太适合,由于需要大量的复制操作,所以效率很低
标记-整理算法(老年代回收算法)
- 避免内存不连续性
- 不用设置两块内存交换
- 适用于存活率较高的场景
分代收集算法(Minor GC和Full GC)
- 按照对象不同生命周期的不同划分区域而采取不同的垃圾回收算法,进而提高JVM的回收效率
在JDK8之前,java堆内存分为年轻代,老年代和永久代,JDK8只保留年轻代(复制算法)与老年代(标记-整理算法)
GC的分类
- Minor GC(新生代GC):频率快
- 年轻代:尽可能快速收集掉那些生命周期短的对象
- Eden区 0.8
- 两个Survivor区 0.2 1:1
- 默认15次
- 年轻代:尽可能快速收集掉那些生命周期短的对象
- Full GC(老年代GC或Major GC):存放生命周期较长的对象 频率低
- 标记-清除算法 或 标记-整理算法
触发Full GC的条件:
- 老年代空间不足
- 永久代空间不足(JDK8之前)
- JDK8之后取消永久代,并将元空间存储位置改为本地内存,这样就会减少Full GC的次数,提升效率
- Minor GC晋升到老年代的平均大小大于老年代的剩余空间
- 调用System.gc() 对老年代与年轻代均会进行GC,但是该方法只是提醒虚拟机去回收对象,但具体的回收时机还是依靠虚拟机
对象如何晋升到老年代:
- 经历Minor次数依旧存活的对象
- Survivor区中存放不下的对象
- 新生产的大对象
常用的调优参数:
- -XX:SurvivorRatio:Eden和Survivor的比值,默认为8:1
- -XX:NewRatio:老年代与年轻代内存大小的比值
- -XX:MaxTenuringThreshold:对象从年轻代晋升到老年代经过GC次数的最大阈值(默认为15)