JVM垃圾回收机制
自动垃圾收集
自动垃圾收集是查看堆内存,识别正在使用哪些对象以及哪些对象未被删除以及删除未使用对象的过程
正在使用的对象是指,程序的某些部分仍然维护指向该对象的指针
未使用的对象是指,程序的任何部分都不再引用的对象,因此需要回收未引用对象的内存
自动垃圾收集机制也是java非常重要的一大特性
如何确定内存需要被回收
第一步,称为标记.这是垃圾收集器识别哪些内存正在使用而哪些不在使用的
引用计数法
在Java中,引用和对象是有关联的,因此一个简单的办法就是通过引用计数
可达性分析
就是将对象及其引用关系看做一个图,选定活动的对象作为GC Roots;
然后跟踪引用链,如果一个对象和GC Roots之间没有连通性,也就是不存在引用,那么可以标记是不可达对象.
不可达对象不代表可回收, 至少要经过两次标记
可以用作GC Root的对象
- 虚拟机栈中正在引用的对象
- 本地方法栈中正在引用的对象
- 静态属性引用的对象
- 方法区常量引用的对象
引用类型
- 强引用:最常见的普通对象引用,只要还有强引用指向一个对象,就不会回收(new出来的对象)
- 软引用:JVM认为内存不足,才会去试图回收软引用指向的对象.(缓存场景)
- 弱引用:虽然是引用,但随时可能被回收掉
- 虚引用:不能通过它访问对象. 通常是在GC时做一些操作
可达性级别
- 强可达:一个对象可以有一个或多个线程可以通过各种引用访问
- 软可达:只能通过软引用才能访问
- 弱引用:只能通过弱引用访问
- 幻象可达:不存在其他引用,并且finalize过了,只有虚引用指向它
- 不可达:意味着可以被GC了
怎么清除垃圾
第二步, 定义如何清除垃圾
垃圾收集算法
标记-清除(mark-sweep)算法
首先标识出所有要回收的对象,然后清除.
标记-清除算法效率优先,但有内存碎片化问题,不适合特别大的堆;
分代收集算法是基本基于标记-清除算法的思路改进.
复制(Copying)算法
划分两块同等大小的区域,收集时将活着的对象复制到另一块区域. 拷贝过程中将对象顺序放置,就可以避免内存碎片化. 但是复制+预留内存, 有一定的资源浪费, 把可用内存压缩到了原来的一半
标记-整理(mark-compact)
类似于标记-清除,但是为了避免内存碎片化,会在清理过程中将对象移动,已确保对象占用连续的内存空间.
分代收集算法
当前商业虚拟机常用的算法,根据对象存活周期的不同,将内存划分为几块,一般是把堆划分为新生代和老年代,这样就可以根据各个年代的特点采用最适合的收集算发.
在新生代中,每次垃圾收集都有大量对象死去,少量存活,就用复制算法,只需要付出少量存活对象的复制成本就可以完成收集,
而老年代中因为对象存活率高,没有额外的空间对它进行分配担保,必须使用标记-清除或者标记-整理算法来回收,不需要大量的移动对象.
新对象会被分配到新生代(Eden),如果超过-XX:+PretenureSizeThreshold
:设置的大对象直接进入老年代的阈值, 直接存放到老年代
- 新生代与复制算法
新生代大多采取复制算法,新生代一般划分为一块较大的Eden空间和两个较小的Survivor(From Space & To Space),每次使用Eden空间和其中的一块Survivor空间,当进行回收时,将Eden中还存活的对象复制到另一个Survivor,当前的Survivor根据年龄决定去向,当超过阈值就前往老年代,未达到阈值就到另一个Survivor.
对象在Survivor每经历一次GC年龄就加一,当年龄超过15,就会被划分到老年代了 - 老年代与标记整理算法.
因为老年代每次只回收很少的对象, 所以使用标记整理算法.
垃圾收集器
串行收集器Serial
-Serial GC -XX:UseSerialGC
单个线程来执行所有垃圾收集工作,适合单处理器机器,Glient模式下JVM的默认选项, 使用复制算法
-Serial Old -XX:UseSeriaOldlGC
Serial的老年代版本,可以在老年代使用, 它采用了标记-整理(Mark-Compact)算法,区别于新生代的复制算法
并行收集器Parallel
-ParNew GC -XX:+UseParNewGC
新生代GC的实现, 它实际是Serial GC的多线程版本. 可以控制线程数量, 参数: -XX:ParallelGCThreads
最常见的场景是配合老年代的CMS GC工作. 参数: -XX:+UseConcMarkSweepGC
-Parallel Scavenge GC -XX:+UseParallelGC
Parallel Scavenge 收集器也是一个新生代垃圾收集器,同样使用复制算法,也是一个多线程的垃圾收集器,它重点关注的是程序达到一个可控制的吞吐量(吞吐量=运行用户代码时间/(运行用户代码时间+垃圾收集时间)),高吞吐量可以最高效率地利用 CPU 时间,尽快地完成程序的运算任务,主要适用于在后台运算而不需要太多交互的任务。自适应调节策略也是 ParallelScavenge 收集器与 ParNew 收集器的一个重要区别。
-Parallel Old GC -XX:+UseParallelGC
server模式JVM的默认GC选择,整体算法和Serial类似,区别是新生代和老年代GC都是并行进行;
可以设置GC时间或吞吐量等,可以自动进行适应性调整Eden,Survivor大小和MaxTenuringThreshold的值.
也称为吞吐量优先的GC: 吞吐量=用户代码运行时间/(用户代码运行时间+GC时间)
-XX:ParallelGCThreads: 设置用于垃圾回收的线程数. 通常和CPU数相等
-XX:MaxGCPauseMills: 设置最大垃圾收集停顿时间. 值是大于0的整数.
-XX:GCTimeRatio: 设置吞吐量大小,它的值是一个0-100之间的整数.
-XX:+UseAdaptiveSizePolicy: 打开自适应GC策略. 以达到在堆大小,吞吐量和停顿时间之间的平衡点
多线程并发收集器CMS(Concurrent Mark Sweep)
-CMS GC -XX:+UseConcMarkSweepGC
专用老年代,基于标记-清除算法,设计目标是尽量减少停顿时间.
但会占用更多的CPU资源,并和用户线程争抢
-G1 -XX:+UseG1GC
针对大堆内存设计的收集器,兼顾吞吐量和停顿时间, JDK9后为默认选项, 目标是替代GMC;
G1将堆分为多个固定大小区域, 并且跟踪这些区域的垃圾收集进度,同时在后台维护一个优先级列表,每次根据所允许的收集时间,优先回收垃圾最多的区域.