前面提到,在程序运行过程中,垃圾回收主要发生在堆区,下面来看看堆区是怎么实现高效的垃圾回收的。
1 堆区的内存划分
为了进行高效的垃圾回收,JVM将java堆区划分为2个区,分别为年轻代、老年代。
1.1 新生代
主要是用来存放新生的对象,默认占据堆的1/3空间,即新生代与老年代之比为 1 : 2,可通过-XX:NewRatio设置
。由于频繁创建对象,所以新生代会频繁触发MinorGC进行垃圾回收。
新生代又分为 Eden区、ServivorFrom、ServivorTo三个区,三者默认为8:1:1,通过-XX:SurvivorRatio设置
- Eden区:Java新对象的出生地(如果新创建的对象占用内存很大,则直接分配到老年代)。当Eden区内存不够的时候就会触发MinorGC,对新生代区进行一次垃圾回收。
- ServivorTo:保留了一次MinorGC过程中的幸存者。
- ServivorFrom:上一次GC的幸存者,作为这一次GC的被扫描者。
MinorGC:
MinorGC指发生在新生代的垃圾收集动作,非常频繁,回收速度也比较快。一般采用复制算法,如下:
- 首先,把Eden和ServivorFrom区域中存活的对象复制到ServicorTo区域(如果有对象的年龄以及达到了老年的标准,一般是15,则赋值到老年代区)
- 同时把这些对象的年龄+1(如果ServicorTo不够位置了就放到老年区)
- 然后,清空Eden和ServicorFrom中的对象;最后,ServicorTo和ServicorFrom互换,原ServicorTo成为下一次GC时的ServicorFrom区。
1.2 老年代
在新生代中存活很久或很大的对象,一般放在老年代区,老年代的对象比较稳定,所以MajorGC不会频繁执行。
当老年代内存空间不足时,触发MajorGC。
MajorGC:
MinorGC指发生在老年代的垃圾收集动作,很少执行,耗时较长。一般采用标记-清除算法,如下:
- 首先扫描一次所有老年代对象,标记出存活的对象
- 然后回收没有标记(引用)的对象
Full GC:
实际上这些术语在JVM规范并没有定义,只是我们根据现象和结果执行定义的,Full GC指清理整个堆空间(老年代+新生代)
1.3 方法区
经常提到的永久代(元空间)也就是方法区,虽然在物理上与堆共享内存,但实际上与堆相互隔离,因此JVM规范并不要求在方法区进行垃圾回收,但还是可以回收的。
方法区中存储者各种常量池、类元信息等,一般长时间存在,但也可能有一些失效的数据,出现以下情况时可以垃圾回收。
- 常量池中的一些常量没有被引用,则会清理出常量池
- 无用的类会被清理出方法区
- 该类的实例被回收
- 加载该类的ClassLoader被回收
- 该类的Class对象没有被引用 (如没反射)
2. 垃圾对象判断算法
前面提到,在进行垃圾回收时都采用了标记—清除算法,那么怎么判断一个对象该不该回收呢?
2.1 引用计数法(已被淘汰,循环引用失效)
给对象中添加一个引用计数器,每当有一个地方引用它,计数器就加 1;当引用失效,计数器就减 1;任何时候计数器为 0 的对象就是不可能再被使用的。这个方法实现简单,效率高,但是目前主流的虚拟机中并没有选择这个算法来管理内存,其最主要的原因是它很难解决对象之间相互循环引用的问题。
2.2 可达性分析
这个算法的基本思想就是通过一系列的称为 “GC Roots” 的对象作为起点,从这些节点开始向下搜索,节点所走过的路径称为引用链,当一个对象到 GC Roots 没有任何引用链相连的话,则证明此对象是不可用的。
可作为 GC Roots 的对象包括下面几种:
- 虚拟机栈(栈帧中的本地变量表)中引用的对象
- 本地方法栈(Native 方法)中引用的对象
- 方法区中类静态属性引用的对象
- 方法区中常量引用的对象
- 所有被同步锁持有的对象
注意:
- 在可达性分析时,必须停顿所有Java线程。如果在分析过程中对象的引用关系在不断变化,则分析结果的准确性就无法得到保证。
2.3 可达性分析之三色标记法
为什么CMS的GC线程可以和用户线程一起工作?可达性分析不用暂停吗?这一切都是因为三色标记法
三色标记法是可达性分析法的一种,普通的可达性分析法需要停止用户线程,而三色标记法可以实现异步可达性分析。
CMS将对象标记为三种颜色: