【JVM专题】垃圾回收算法与垃圾收集器
一.垃 圾 回 收
主要解决3个问题:
1.如何判定对象为垃圾对象
引用计数法 可达性分析法
2.如何回收
回收策略: 标记-清除算法 复制算法 标记-整理算法 分代收集算法
垃圾回收器: Serial Parnew Cms G1
3.何时回收
(1)引用计数法:
所以当计数值为0的时候,垃圾回收器便开始收集。但通常情况下都不使用该种策略,因器其并不能完全回
收,例如,在堆内存中若存在对象之间的引用,便不能被回收。
此时,若切断引用。在堆中的整体由于被对方引用,所以不会被回收,此时垃圾回收效率低。
eg:
public class testGC {
private Object instance;
public testGC() {
byte[] b = new byte[20*1024*1024];
}
public static void main(String[] args) {
testGC t1 = new testGC();
testGC t2 = new testGC();
t1.instance = t2;
t2.instance = t1;
t1 = null;
t2 = null;
System.gc();
}
}
配置JVM参数
打印得到的日志
[GC (System.gc()) [PSYoungGen: 22446K->648K(37888K)] 42926K->21136K(123904K), 0.0019986 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[Full GC (System.gc()) [PSYoungGen: 648K->0K(37888K)] [ParOldGen: 20488K->546K(86016K)] 21136K->546K(123904K), [Metaspace: 2748K->2748K(1056768K)], 0.0112990 secs] [Times: user=0.00 sys=0.02, real=0.01 secs]
Heap
PSYoungGen total 37888K, used 328K [0x00000000d6600000, 0x00000000d9000000, 0x0000000100000000)
eden space 32768K, 1% used [0x00000000d6600000,0x00000000d6652030,0x00000000d8600000)
from space 5120K, 0% used [0x00000000d8600000,0x00000000d8600000,0x00000000d8b00000)
to space 5120K, 0% used [0x00000000d8b00000,0x00000000d8b00000,0x00000000d9000000)
ParOldGen total 86016K, used 546K [0x0000000083200000, 0x0000000088600000, 0x00000000d6600000)
object space 86016K, 0% used [0x0000000083200000,0x00000000832888d0,0x0000000088600000)
Metaspace used 2755K, capacity 4486K, committed 4864K, reserved 1056768K
class space used 295K, capacity 386K, committed 512K, reserved 1048576K
(2)可达性分析法
通过定义GCRoot来进行从栈内存到堆中寻找,能被找到的说明是活着的对象,不能被找到的说明是垃圾对象,就要被回收。
模型图:
如果切断了引用,则在堆中的整个一团,都将被当醉垃圾被回收。
(3)标记清除算法
标记清除算法是其他算法的基础,但其本身有两个要解决的问题:效率问题 空间问题
图中橙色表示正在被使用的空间,黄色表示可被回收的空间 。这样回收之后会造成空间的不连续,
内存分配的时候效率就很低。又会进行多次垃圾回收。
(4)复制算法(主要在新生代)
解决标记清除的效率问题
下面描述一下大概过程:模拟两个独立的区域,在上面的内存中,被标记回收的对象,清除以后。未被使用的对象,
将被连续的安排在下面的区域中,这样,当划分对象的时候,其效率大大提高。同样的,在下面的区域,被标记清除
之后,未被使用的内存也将被移动到上半部分,再被使用。
详细说明:
内存的大概分布如下 在Eden区,只要创建新的对象,就会被扔到这个区域内。 两块Survivor为上图中
的两块区域。Tenured Gen为内存担保,防止内存不够,作为缓冲。
内存变化大致如下图,其被浪费的空间也仅占10%左右,这是一个可以被考虑的范围。
(5)标记整理算法(适用于老年代)
对于老年代部分的内存,回收效率较低,会后垃圾较少的情况下,采用该算法最合适。
标记整理算法可理解为 标记--整理--清楚三个部分,在上图中被标记的算法向右移动,
右边的未被标记的算法也需要往左边移动,这样右边便会被整体清除回收,仅仅只是移动。
(6)分代收集算法
分代收集算法并不是一个行的算法。他是有复制算法和标记整理算法的组合而实现的,
当处于新生代,则采用复制算法。当处于老年代则采用标记整理算法。分代收集算法
会根据不同的情况采用不同的策略。
(7)Serial垃圾收集器
关于其特性:
单线程执行模式大致如下:
首先理解一下,单线程垃圾收集器,可能很多人会理解,单线程效率不是非常低,于是很多人就受不了
其实不然,比如你在收集垃圾的时候又在创建垃圾,这样你就会永远无法收集完垃圾。如上图,在其他
线程执行过程中,暂停其他线程,单独开启垃圾回收线程,当垃圾回收完毕,则可以进行其他线程,这
样来回往复的进行垃圾收集与回收。但缺点就是,在高并发情况下,其执行效率较低。
(8)Parnew垃圾收集器
该收集器为多线程执行模式
使用复制算法收集(新生代)
根据图可看出,与Serial垃圾收集器的不同是,在收集线程执行的过程中是多个垃圾收集线程执行,
在高并发的情况下执行效率较高,降低垃圾收集的时间,提高用户体验感。其实在某些情况下,
例如桌面客服端下,不如Serial执行效率高。
要注意的一个点是,通常在使用Cms收集器对老年代进行收集的时候,不能同时使用Parallel进行收集,所以通
常情况下与parnew收集器进行组合使用。
(9)Parallel垃圾收集器
parallel与parnew的大致功能相同,但他们的不同点在于关注点不同,即使用场景不同。
parallel可进行参数的改变进行控制吞吐量:
垃圾回收器的最大停顿时间以毫秒的单位,若时间过短,则其所能收集的内存也变小,
但其收集的频率将变大。所以相当于一杆秤,根据应用场景不同要权衡好时间。
小结:
parnew 适用于例如和用户交互的场景,垃圾收集器停顿时间短,用户体验感好。
parallel适用于服务端开发,在高并发的情境下