JVM系列二:垃圾回收

什么时候回收对象

引用计数法 

1、原理:为对象添加一个引用计数器,当对象增加一个引用时计数器加 1,引用失效时计数器减 1。引用计数为 0 的对象可被回收。

2、缺点:无法解决循环引用问题

可达性分析

1、原理:以 GC Roots 为起始点进行搜索,可达的对象都是存活的,不可达的对象可被回收。

2、可作为GC Root的对象:

  • 虚拟机栈中局部变量表中引用的对象
  • 本地方法栈中 JNI 中引用的对象
  • 方法区中类静态属性引用的对象
  • 方法区中的常量引用的对象

方法区回收

1、主要是对类的卸载和对常量池的回收,对于大量引用动态代理和反射的场景下,类的卸载是具有重要意义的

2、类的卸载需满足以下三个条件,但是就算三个条件都满足也不一定就被卸载

  • 该类所有的实例都已经被回收,此时堆中不存在该类的任何实例。
  • 加载该类的 ClassLoader 已经被回收。
  • 该类对应的 Class 对象没有在任何地方被引用,也就无法在任何地方通过反射访问该类方法。

finalize方法

1、类似 C++ 的析构函数,用于关闭外部资源。但是 try-finally 等方式可以做得更好,并且该方法运行代价很高,不确定性大,无法保证各个对象的调用顺序,因此最好不要使用。

2、当一个对象可被回收时,如果需要执行该对象的 finalize() 方法,那么就有可能在该方法中让对象重新被引用,从而实现自救。

自救只能进行一次,如果回收的对象之前调用了 finalize() 方法自救,后面回收时不会再调用该方法

四种引用

不管是引用计数法还是可达性分析,引用的判断与计数都是很重要的

强引用

1、特点:不会被回收

2、构造方式:new

 1 Object object = new Object(); 

软应用

1、特点:只有在内存不够的时候才会被回收

2、构造方式:SoftReference

1 Object object = new Object();
2 SoftReference<Object> sf = new SoftReference<Object>(object);
3 object = null;//使对象只能被软引用关联

 

弱引用

1、特点:下一次内存回收的时候一定会被回收

2、构造方式:WeakReference

1 Object object = new Object();
2 WeakReference<Object> sf = new WeakReference<Object>(object);
3 object = null;

 

虚引用

1、特点:没有办法得到一个虚引用对象,设置它的唯一目的就是在系统回收它的时候得到一个通知

2、构造方式:PhantomReference

1 Object object = new Object();
2 PhantomReference<Object> sf = new PhantomReference<Object>(object);
3 object = null;

 

垃圾收集算法

标记-清除算法

1、过程

(1)标记阶段:程序会检查每个对象是否为活动对象,如果是活动对象,则程序会在对象头部打上标记。

(2)清除阶段:会进行对象回收并取消标志位,另外,还会判断回收后的分块与前一个空闲分块是否连续,若连续,会合并这两个分块。

回收对象就是把对象作为分块,连接到被称为 “空闲链表” 的单向链表,之后进行分配时只需要遍历这个空闲链表,就可以找到分块。

在分配时,程序会搜索空闲链表寻找空间大于等于新对象大小 size 的块 block。如果它找到的块等于 size,会直接返回这个分块;

如果找到的块大于 size,会将块分割成大小为 size 与 (block - size) 的两部分,返回大小为 size 的分块,并把大小为 (block - size) 的块返回给空闲链表。

2、图解

 

 

3、缺点

  • 标记和清除的过程效率都不高
  • 可能产生大量内存碎片

 

标记-整理算法

1、过程

让所有存活的对象都向一端移动,然后直接清理掉端边界以外的内存。

2、图解

 

 

3、缺点

每次都要移动存活的对象

 

复制算法

1、过程

将内存划分为大小相等的两块,每次只使用其中一块,当这一块内存用完了就将还存活的对象复制到另一块上面,然后再把使用过的内存空间进行一次清理。

现在的商业虚拟机都采用这种收集算法回收新生代,但是并不是划分为大小相等的两块,而是一块较大的 Eden 空间和两块较小的 Survivor 空间,每次使用 Eden 和其中一块 Survivor。在回收时,将 Eden 和 Survivor 中还存活着的对象全部复制到另一块 Survivor 上,最后清理 Eden 和使用过的那一块 Survivor。

HotSpot 虚拟机的 Eden 和 Survivor 大小比例默认为 8:1,保证了内存的利用率达到 90%。如果每次回收有多于 10% 的对象存活,那么一块 Survivor 就不够用了,此时需要依赖于老年代进行空间分配担保,也就是借用老年代的空间存储放不下的对象。

2、图解

 

3、缺点

每次只能用到局部的内存

 

分代收集算法

1、按照对象生存周期将内存分为不同的区域主要分成新生代和老年代,不同的区域采取不同的收集算法

2、新生代:采取复制算法

   老年代:采取标记-清理算法或者标记-整理算法

 

垃圾收集器

 

新生代收集器

Serial收集器

 

ParNew收集器

 

Parallel Scavenge收集器

 

与 ParNew 一样是多线程收集器。

其它收集器目标是尽可能缩短垃圾收集时用户线程的停顿时间,而它的目标是达到一个可控制的吞吐量,因此它被称为“吞吐量优先”收集器。这里的吞吐量指 CPU 用于运行用户程序的时间占总时间的比值。

 

老年代收集器

 

Serial Old收集器

 

Parallel Old收集器

 

CMS收集器

 

G1收集器

 

 

搭配关系

 

比较总结

 

收集器 收集算法 新生代/老年代 备注
Serial 复制算法 新生代
ParNew 复制算法 新生代
Parallel Scavenge 复制算法 新生代  
Serial Old 标记-整理算法 老年代  
Parallel Old 标记-整理算法 老年代
CMS 标记-清除算法 老年代  
G1 标记-整理算法 新生代+老年代  
posted @ 2019-06-15 13:33 huanglf714 阅读(...) 评论(...) 编辑 收藏