深入理解java虚拟机(二)

垃圾回收

Posted by Kinsomy on April 29, 2019

1.内存回收

1.1 判断对象是否存活

垃圾收集器会对不再被使用的对象进行GC操作,那么就要判断一个对象是否存活,有以下几种算法:

1.1.1 引用计数算法

概述:对堆内存中的每个对象添加一个引用计数器,有其他地方引用它计数器加一,引用断开就减一。

优点:算法时间简单,判端高效。

缺点:难以解决对象之间循环引用的问题。

class O {
    public Object o;
}

O oa = new O();
O ob = new O();
oa.o = ob;
ob.o = oa;

1.1.2 可达性分析算法

概述:GC Roots作为根引用点,当一个对象从GC Roots往下追溯无法被到达时,那这个对象就不可用,可以被GC。



图上左边的对象都是GC Roots可达的,不可回收,右边的对象是可以回收的。

可以作为GC Roots的对象包括:

  • 虚拟机栈(栈帧中的本地变量表)中引用的对象。

  • 方法区中类静态属性引用的对象。

  • 方法区中常量引用的对象。

  • 本地方法栈中JNI(即一般说的Native方法)引用的对象。

1.2 引用类型

强引用是正常new出来的引用,垃圾收集器不会回收强引用对象。

软引用是有用但不是必需保留的对象,弱于强引用,当系统内存宽裕时不会回收这些对象,但是如果内存紧张,将要有溢出风险时,会考虑回收软引用对象。

弱引用是比软引用更弱一些的引用对象,弱引用对象只能存活到下一次GC到来之前,不管下一次的GC时内存是否充裕,弱引用对象都会回收。

虚引用是最弱的引用,虚引用的作用仅仅是在运行时该对象被回收后获得通知。

1.3 垃圾收集算法

1.3.1 标记-清除算法

概述:通过上面讲到的算法来判断每个对象是否要被回收,如果要被回收,则加上标记,标记完成后对所有被标记的对象统一回收。

缺点:标记和清除的过程效率不高;清除之后会有大量不连续的内存碎片,这些被清除后产生的可用内存不能组成连续内存,无法给内存需求较大的对象分配空间。



1.3.2 复制算法

概述:将内存分成两半,每次只使用其中一半的区域,当一半内存耗尽时,将还存活的对象复制到另一半区域,同时回收当前部分的内存。

优点:解决了标记-清除算法带来的内存碎片问题。

缺点:只能使用一半内存,另一半处于空闲,太过浪费。

 HotSpot虚拟机的复制算法使用的是一块Eden空间和两块Survivor控件,大小是8:1:1,用来回收新生代对象,(新生代对象被回收的概率大)。每次使用Eden控件和一块survivor空间,回收后将存活的对象复制到另一块survivor空间,然后清除Eden和上一个survivor。这样有效地降低内存浪费。但是对于老年代的对象回收则不那么适用。

1.3.3 标记-整理算法

标记的过程和标记-清除算法类似,但是在清除无用对象之后,还需要将存活的对象向内存空间的一端移动,并更新引用其对象的指针。

优点:不会产生内存碎片。

缺点:增加了移动过程,增加算法成本。

1.3.4 分代收集算法

商业虚拟机普遍采用的算法,将java堆分为“新生代”和“老年代”,对新生代采用复制算法,对老年代采用标记-整理算法。

2 垃圾收集器

2.1 Parallel Scavenge收集器

Parallel Scavenge收集器的目的是想要达到一个可控制的吞吐量,它不是一味的缩短垃圾收集停顿时间或者控制每次GC的大小,而是吞吐量的精确控制。

吞吐量是CPU执行用户代码的时间和CPU运行总时间的比值,这个比值越大,吞吐量越大,CPU的时间利用效率越高。

-XX:MaxGCPauseMillis:控制每次GC最大暂停时间

-XX:GCTimeRatio: 控制吞吐量比率,是一个1-100的整数值,计算公式为1/(1+ratio)

-XX:UseAdaptiveSizePolicy: 自适应调节策略,可以根据当前系统性能情况自动调节参数选择合适的GC停顿时间和吞吐量,做到自动优化。

2.2 CMS收集器

CMS收集器全称Concurrent Mark Sweep,目标是为了获取最短的GC停顿时间,基于“标记-清除”算法。分为四个步骤:

初始标记 
快速标记和GC Roots直接连接的对象,stop the world

并发标记 
追溯GC Roots关联的对象

重新标记 
因为是并发收集,修正因期间执行代码标记改变的部分,stop the world,时间长于初始标记,远短于并发标记

并发清除
清除不存活的对象

优点:并发手机,低停顿时间

缺点:

1、CPU资源敏感,
2、无法处理浮动垃圾(上一次清理阶段用户继续产生的垃圾)
3、标记-清除算法产生内存碎片

2.3 G1收集器

Garbage-First是从JDK 7正式使用的收集器,到最新的JDK 10都一直在使用,采用的“标记-整理”,局部采用“复制”算法,不会像CMS一样产生内存碎片。

可以看文章Getting Started with the G1 Garbage Collector

基本步骤:

初始标记
并发标记
最终标记
筛选回收

参考资料