欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

JVM系列二:垃圾回收

程序员文章站 2022-04-29 18:53:55
什么时候回收对象 引用计数法 1、原理:为对象添加一个引用计数器,当对象增加一个引用时计数器加 1,引用失效时计数器减 1。引用计数为 0 的对象可被回收。 2、缺点:无法解决循环引用问题 可达性分析 1、原理:以 GC Roots 为起始点进行搜索,可达的对象都是存活的,不可达的对象可被回收。 2 ......

什么时候回收对象

引用计数法 

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、图解

 JVM系列二:垃圾回收

 

3、缺点

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

 

标记-整理算法

1、过程

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

2、图解

 JVM系列二:垃圾回收

 

3、缺点

每次都要移动存活的对象

 

复制算法

1、过程

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

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

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

2、图解

JVM系列二:垃圾回收

 

3、缺点

每次只能用到局部的内存

 

分代收集算法

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

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

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

 

垃圾收集器

 

新生代收集器

serial收集器

JVM系列二:垃圾回收

 

parnew收集器

JVM系列二:垃圾回收

 

parallel scavenge收集器

 

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

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

 

老年代收集器

 

serial old收集器

JVM系列二:垃圾回收

 

parallel old收集器

JVM系列二:垃圾回收

 

cms收集器

JVM系列二:垃圾回收

 

g1收集器

 JVM系列二:垃圾回收

 

搭配关系

JVM系列二:垃圾回收

 

比较总结

 

收集器 收集算法 新生代/老年代 备注
serial 复制算法 新生代 JVM系列二:垃圾回收
parnew 复制算法 新生代 JVM系列二:垃圾回收
parallel scavenge 复制算法 新生代  JVM系列二:垃圾回收
serial old 标记-整理算法 老年代  JVM系列二:垃圾回收
parallel old 标记-整理算法 老年代 JVM系列二:垃圾回收
cms 标记-清除算法 老年代  JVM系列二:垃圾回收
g1 标记-整理算法 新生代+老年代  JVM系列二:垃圾回收