JVM垃圾回收机制概述
如何判断“垃圾”
在JAVA中是通过引用来和对象进行关联的,必须通过引用来操作对象。一般的,如果一个对象没有任何引用与之关联,那么该对象基本不可能在其他地方被使用。
JAVA中常用的判断对象是否“垃圾”的方法有引用计数法和可达性分析法。
引用计数法的特点是实现简单,而且效率较高。但这种方法无法解决循环引用的问题。因此这种方法很快就过时了。(python仍然采用这种方法)
为了解决上述问题,JAVA采取了另一种方法,即可达性分析法。该方法的基本思想为通过一系列“GC Roots”作为起点进行搜索,如果有一个对象没有与任何“Root”存在可达路径,那么就认为该对象是不可达的。然而,不可达对象不会被立即回收,还需要经历至少两次标记,如果在两次标记的过程中,对象的finalize()方法没有让它与任何“Root”重新连接,那么该对象就会被回收。
可达性分析法原理:将JAVA堆中所有的引用与对象在内存中生成一张图,由“GC Roots”开始全部遍历搜索。没有任何路径可达的对象将会被“标记”一次。
可以作为“GC Roots”的对象有:
- 虚拟机栈中的引用对象,如栈帧中的本地变量表
- 方法区中静态属性所引用的对象
- 方法区中常量
- 本地方法栈中native方法引用的变量
对象中有四种引用:分别是强、软、弱、虚
强引用:只要对象引用存在,便不会被会回收
软引用:非必需对象,会在内存不足时进行回收
弱引用:会在下一次垃圾回收中被回收
虚引用:几乎没有影响,仅能用来追踪垃圾回收过程
常用的垃圾收集算法
标记消除法
特点是:实现简单,回收速度快,但回收过的内存不连续。
基本流程:分为两个阶段,即标记阶段和消除阶段。从根集合进行扫描,标记需要被回收的对象,然后对这部分对象进行回收。
该策略不会移动对象,对象存活多时较为高效。
适合老年代区域的回收,代表性工具是CMS。
标记整理法
特点是:回收后空间连续,但运行时间较长(主要是整理部分)。
基本流程:该方法现对对象进行存活标记,然后将存活的对象转移至内存的一端移动,再清除边界外的所有内存。
该方法在标记—清除的基础上,将对象进行移动,提高了时间成本,但解决了内存碎片问题。
适合老年代回收,代表工具parallel Old 和Serial old 工具。
复制算法
特点:运行高效,不易产生内存碎片,但可使用的内存空间遭到了削减。
基本流程:将内存划分成两部分,每次仅使用其中一个部分,当该内存用完时,将存活对象复制到另一块内存中去,再将这部分内存全部清除。
两个区域的是为了减少风险率,有一个区要参与回收,也要参与存储,只有少量的空间浪费,同时也减少对老年代的依赖。
该方法在少量复制对象的情况下运行较为高效,即存活对象较少。使用老年代在内存不足时进行担保,因此仅适合新生代垃圾的回收。代表工具是serial new、parallel new和parallel scanvage 。
在使用该方法时,注意不要将内存均分,应当使回收区域内存的比例
降低,增加另一部分的比例,从而增加空间的利用率。
GC收集器
串行收集器:该类收集器一般是单线程收集器。实现简单,但在进行垃圾回收工作时会暂停所有用户进程。
- serial new(年轻代,复制算法)
- serial old(老年代,标记整理算法)
并行收集器:该类收集器一般是多线程的收集器。
- paraller new(年轻代,serial new的多线程版本,复制算法,Stop the world)
- Paraller Scavenge(年轻代,复制算法,并行收集器,回收期间不暂停用户线程,吞吐量可控)
- Paraller Old(老年代,标记整理算法)
- CMS(老年代,并发标记-清除法,短回收停顿)
G1收集器
面向服务端应用的收集器,充分利用多CPU,多核环境。是一款并行与并发收集器,并且能建立可预测的时间停顿模型。
可以同时处理年轻代与老年代的垃圾回收问题。
整体上采用标记整理的方法,局部采取复制算法。并行标记对象,并发清除对象,可以做到不暂停用户线程。
其特点是将内存Heap均分为很多小块,每个小块属于不同的年代,分布在内存中的同类块结合成一代。
Minor GC 和 Full GC发生的时间
Minor GC:JVM无法在年轻代为新对象分配空间时触发。对象的分配频率越快,越容易触发Minor GC。
Full GC:
- 调用System.gc()函数时,可能会触发Full GC
- 分配的对象过大,老年代的剩余空间不足以存储该对象
- 方法区空间不足,类加载、反射调用的方法过多。
- 统计得到的需要晋升老对象的新对象的平均值大于老年代的剩余空间。