JVM深入学习(四)垃圾回收器

导语:java的垃圾回收是对java的内存管理,C语言中,程序员既是内存的上帝,又是内存的仆人,有内存的权限分配,也需要为每块内存进行清理。java中设计了内存的自动管理机制,就是垃圾回收器。

垃圾回收器要做的三件事情:回收什么,什么时候回收,如何回收

一.回收什么

之前我们讲过java的内存模型中,程序计数器、虚拟机栈、本地方法栈都是基本固定大小,变动不大,故无需回收机制。堆内存和元数据区的大小不确定,会较大变动,堆又是内存模型中分配的内存最大的一块,所以需要回收。

java是面向对象的语言,万物皆对象,所以回收的也是对象,触发回收时,不能再被使用的对象会被进行回收。

判断对象是否存活的算法:

a)引用计数法

原理:在对象中添加一个引用计数器的属性,有一个地方引用+1,引用失效-1,为0表示未在使用

缺点:目前主流虚拟机不使用此算法,因为例外情况太多,必须配合大量的额外处理,比如循环引用

//这样的循环引用,实验引用计数法,无法回收
ObjectA  objecta = new ObjectA();
ObjectB  objectb = new ObjectB();
objecta.instance = objectb;
objectb.instance = objecta;
objecta = null;
objectb = null;

b)可达性分析算法

原理:遍历GC Roots节点,从GC Roots到这个对象不可达时,表示对象不再被使用。

image

GC Roots有哪些(GC Roots主要是运行中的对象 和 常驻的变量):

在虚拟机栈中引用的对象

元数据区的静态变量

元数据区的常量

在本地方法栈中引用的对象

java虚拟机内部的引用,如基本数据类型对应的Class对象

所有被同步锁持有的对象

反映Java虚拟机内部情况的JMXBean、本地代码缓存等

...

c)finalize()逃脱死亡

被判定为死亡的对象,不会立即被回收,而是进行标记,然后GC会判断该对象是否覆盖了finalize方法,若未覆盖,则直接将其回收。否则,若对象未执行过finalize方法,将其放入F-Queue队列,由一低优先级线程执行该队列中对象的finalize方法。执行finalize方法完毕后,GC会再次判断该对象是否可达,若不可达,则进行回收,否则,对象“复活”。

同一个对象finalize()仅会执行一次

不建议使用,早期兼容C语言的创造,可以用try{}catch() 实现类似功能。

二.什么时候回收

各个垃圾回收器的回收条件不同,大致都是内存不够分配对象时,会触发垃圾回收器回收,然后暂停用户线 程,清理内存。

各垃圾回收器在下面会讲解,主要讲下jdk1.8 服务器模式下默认垃圾收集器:Parallel Scavengen+Paraller Old

Parallel Scavengen+Paraller Old的触发机制

Minor GC触发机制:

当年轻代满时就会触发Minor GC,这里的年轻代满指的是Eden代满,Survivor满不会引发GC。

Full GC触发机制:

(1)调用System.gc时,系统建议执行Full GC,但是不必然执行

(2)老年代空间不足

(3)方法区空间不足

(4)通过Minor GC后进入老年代的平均大小大于老年代的可用内存

(5)由Eden区、survivor space1(From Space)区向survivor space2(To Space)区复制时,对象大小大于To Space可用内存,则把该对象转存到老年代,且老年代的可用内存小于该对象大小

三.如何回收

分代收集理论:

弱分代假说:绝大多数对象都是朝生夕灭的

强分代假说:熬过多次垃圾收集过程的对象,越难以被回收。

基于分代收集理论,java主流虚拟机一般把内存区块分成新生代和老年代

新生代:主要存放新生的对象,每次垃圾回收幸存后,年龄+1

老年代:年龄达到老年线(一般是15),进入老年代

回收算法:

标记-清除算法

最早出现的算法,分为两个阶段,标记阶段和清除阶段

缺点:执行效率低,对内存中所有对象都要进行标记,然后找到需要清除的,全部清除,内存碎片多,更容器触发gc

image

标记-复制算法

又称半区复制,将内存分成两个一样大小的内存块,标记需要清理的对象,把仍然存活的对象复制到另一半区域。

缺点:有一半的空间浪费。

image

标记-整理算法

标记同标记-清除算法一致,标记后将存活的对象整理到内存空间的一侧。

常用于老年代。

image

HotSpot JVM把年轻代分为了三部分:1个Eden区和2个Survivor区(分别叫from和to)。默认比例为8:1:1

平时使用的是Eden+Survivor From分区,Survivor To 分区空闲。当发生Minor gc时,将Eden区仍存活的对象复

制到Survivor To区,将Survivor From区年龄到了的对象晋升到老年代,没到的对象,复制到Survivor To区。

image

四.经典的垃圾回收器

1. Serial

Serial收集器是古老的收集器,就像它的名字一样,它是单线程的收集器。

简单高效

垃圾收集器stop the world

客户端模式下默认的新生代收集器

2. parNew

Serial的多线程版本

jdk1.5时 与CMS一起使用的新生代收集器

3. Parallel Scavengen

jdk1.8服务器模式下 默认的新生代收集器

可设置吞吐量

多线程收集器

基于标志-复制算法

4. Serial Old

Serial收集器老年代版本

单线程

客户端模式下使用

5. Paraller Old

Parallel Scavengen 老年代版本

多线程收集器

基于标记-整理算法

jdk1.8服务器模式下 默认的老年代收集器

6. CMS

以最短停顿时间为目标,所有的垃圾清理都是有停顿时间的,CMS基于前面讲的标记-清除算法,无需移动内存,停顿时间就会降下去

基于标志-清除算法

初始标记

并发标记

重新标记

并发清除

7. G1

jdk9下服务端默认收集器

全栈收集器

堆内存化整为零,拆分成小块内存,每次只清理局部内存块,这样在单体应用巨大的情况下停顿时长也很短

8. Shenandoah和ZGC

低延时收集器

五. 衡量垃圾收集器的指标

内存占用 内存使用百分比

吞吐量 (程序运行时间-暂停时间)/程序运行时间 吞吐量越高算法越好

低延时 暂停时间越短算法越好

随着硬件技术的发展,内存占用不再是系统关注的重点,吞吐量和低延时才是程序员关心的重点,根据需求选择

合适自己的收集器

参考文档:https://blog.csdn.net/Alpha_Paser/article/details/82533128

深入理解Java虚拟机:JVM高级特性与最佳实践(第3版)

你可能感兴趣的:(JVM深入学习(四)垃圾回收器)