1 先谈Finalize()

finalize()能做的所有工作,使用try-finally或者其他方式都可以做得更好、更及时,所以笔者建议大家完全可以忘掉Java语言中有这个方法的存在。

——《深入理解JVM》

finalize()方法确实可以实现一次对象的自救,但是其不确定性和昂贵的运行代价都表明这个方法的使用需要十分的慎重。那么finalize()在什么时期起作用又是如何实现对象的自救的呢?首先我们要理解虚拟机在扫描到死亡对象的时候并不是直接回收的,而是进行一次标记并且筛选,筛选的条件就是其对象的finalize方法是否有必要执行。如果当前对象没有重写finalize方法或者已经调用过一次finalize方法,那么则视为没有必要执行,此时便失去自救的机会,放入"即将回收"集合中。

否则的话,则将对象放入一个叫F-Queue的队列中,稍后虚拟机将一个个的执行队列中对象的finalize方法(就是在此处对象可以在finalize方法中将自身关联到引用链,从而暂时逃脱被回收的命运),需要注意的是虚拟机保证执行但不保证执行完finalize方法,原因是如果finalize方法执行时间过长或者陷入死循环,则可能让系统奔溃。全部执行之后,虚拟机将对队列的对象重新标记一次,如果还不在引用链中则GG,否则将其移出"即将回收"集合。下面例子参考《深入理解JVM》实现自救并且验证只能自救一次的过程。

public classTestForGc {/**定义一个根节点的静态变量*/

public staticTestForGc INSTANCE;/*** 重写finalize方法,让其被标记为有必要执行并且加入F-Q

*

*@throwsThrowable*/@Overrideprotected void finalize() throwsThrowable {super.finalize();

System.err.println("finalize method in TestForGc Class invoked!");//将自身关联到根节点中,实现自救

INSTANCE = this;

}public static void main(String[] args) throwsInterruptedException {

INSTANCE= newTestForGc();

INSTANCE= null;

System.gc();//睡眠1S,保证F-Q中的方法执行完毕

TimeUnit.SECONDS.sleep(1);if(Objects.nonNull(INSTANCE)) {

System.out.println("i successfully save myself by finalize method!");

}else{

System.out.println("i am dead :(");

}/** 下面验证finalize方法只能调用一次

* 几乎完全一样的代码,却是不同的结局*/INSTANCE= null;

System.gc();//睡眠1S

TimeUnit.SECONDS.sleep(1);if(Objects.nonNull(INSTANCE)) {

System.out.println("i successfully save myself by finalize method again!");

}else{

System.out.println("couldn't invoke finalize again, i am dead :(");

}

}

}

执行结果:

2 垃圾回收器

如果说回收算法是接口,那么垃圾回收器就是这些接口的实现类,共有7种回收器,接下来一一罗列。

2.1 Serial垃圾回收器

Serial是一种单线程垃圾回收器,在工作的时候的时候会暂停所有的用户线程,也就是"stop-the-world",虽然单线程代表了用户线程的停顿,但是也意味着其不用进行线程的交互从而有更高的收集 效率。Serial采用复制算法,是Client端新生代的默认垃圾回收器。其工作图类似于:

2.2 ParNew垃圾回收器。

ParNew是Serial回收器的多线程版本,是Server端新生代的默认回收器,除了并行多线程之外,其他包括实现都是一模一样,当然也是采用复制算法。还有一点重要的是,新生代的收集器除了Serial之外,只有ParNew能跟年老代的CMS合作,其在低CPU的情况下效率比Serial低,但是在多个CPU的情况下要好的多。其工作图:

2.3 Parallel Scavenge垃圾回收器

跟ParNew类似,作用于新生代,并行多线程并且也是采用复制算法。但是其关注的点却不同,其着重的是一种叫做"吞吐量"的东西。所谓的"吞吐量"=运行用户代码的时间 / (运行用户代码的时间 + GC时间),也就是说其更加注重用户代码运行时间而不是减少GC停顿时间。相对于其他收集器来说,可以更加高效的利用CPU,更加适合作为在后台运算而不大需要交互的任务。Parallel收集器提供了两个比较重要的参数。

-XX:MaxGCPauseMillis:表示收集器将尽可能的在这个参数设定的毫秒数内完成回收工作。但这并不代表其设置的越低越好,缩减回收时间是通过减少吞吐量换来的,如果设置得太低可能导致频繁的GC。

-XX:GCTimeRatio:表示代码运行时间和垃圾回收时间的比率,比如说设置为19,那么则垃圾回收时间占比为 1 / (1+19) = 5%,默认是99。

2.4 Serial Old垃圾回收器

Serial的年老代版本,同Serial基本相似,不同的是采用的是标记-整理算法实现,作为Client端默认的年老代收集器。如果在Server端的话,那么其主要作用有二:

1、跟新生代的Parallel Scavenge收集器配合。

2、做一个有价值的"备胎":当CMS垃圾回收器因为预留空间问题放不下对象而发生Concurrent Mode Fail时,作为其备选方案执行垃圾回收。

2.5 Parallel Old垃圾回收器

Parallel Scavenge的年老代版本,多线程并行,同样注重吞吐量,使用标记-整理算法。这个收集器可以跟新生代的Parallel Svavenge一起搭配使用,在注重吞吐量和CPU资源敏感的场合中是一对很好的组合。

2.6 CMS垃圾回收器

来了,它来了!CMS垃圾回收器被当做是具有划时代意义的、真正实现并发的垃圾回收器,总而言之=》

,--^----------,--------,-----,-------^--,

| ||||||||| `--------' | O

`+---------------------------^----------|

`\_,-------, _______________________强__|

/ XXXXXX /`| /

/ XXXXXX / `\ /

/ XXXXXX /\______(

/ XXXXXX /

/ XXXXXX /

(________(

`------'

CMS是一款并发的垃圾回收器,但并不代表全程都不需要停顿,只是大部分时间是跟用户线程一起执行的。其整个GC过程中总共有4个阶段。

1、初始标记:简单的标记所有的根节点,需要暂停所有的用户线程,即"stop-the-world",耗时较短。关于GCRooots的过程可以看下另一篇文章——垃圾回收(一)。

2、并发标记:跟用户线程一起工作,寻找堆中的死亡对象,整个过程耗时最长。

3、重新标记:再次扫描,主要对象是并发标记过程中又新增的对象,也就是验漏。多线程,需要STW,时间相对并发标记来说短。

4、并发清除:GC线程跟用户线程一起执行,清除标记的死亡对象,"浮动垃圾"在此阶段产生。

然而,优秀如CMS也会有不足之处,总共四个阶段的标记及清除算法的实现必定为其带来一些使用的麻烦。

缺点:

1、占用一定CPU资源:其有两个阶段需要并发跟用户线程一起执行,也就是说要跟用户线程抢占CPU的时间片,会占用一定的CPU资源,如果CPU资源不太优质的情况下,可能会造成不小的影响。

2、空间利用率不能达到最大:由于并发清除时用户线程也在运行,那么在GC结束前必定会产生一些额外的垃圾,那么就必须给这些垃圾预留一定的空间,否则会导致内存不足从而报"Concurrent Mode Failure",此时虚拟机便启用后备方案——使用Serial Old来进行垃圾回收,进而浪费更多的时间。

3、内存碎片导致提前FullGC:CMS采用的是标记-清除算法,也就是说会产生内存碎片,那么可能出现大对象放不下的情况,进而不得不提前进行一次FullGC。为了解决这个问题,虚拟机提供了两个参数-XX:+UseCMSCompactAtFullCollection和-XX:CMSFullGCsBeforeCompaction,分别表示CMS顶不住要进行FullGC的时候进行内存的整理(整理的过程中无法并发,停顿时间不得不变长) 和进行多少次不压缩的FullGC之后来一次整理的GC(默认0次,表示每次都进行内存整理)。

2.7 G1垃圾回收器

G1是一个新秀垃圾回收器,被赋予了很大的使命——取代CMS。G1作为新时代的垃圾回收器,相对于其他垃圾回收器来说有许多优势。

1、并行和并发:G1可以利用现在的硬件优势,缩短GC时stop-the-world的停顿时间,并且GC的时候同时也能让用户线程执行。

2、分代收集:跟其他垃圾回收器不同,G1没有物理上的年老代和新生代,其将内存分成了多个独立的Region,每个Region都可能表示属于新生代还是年老代,所以不需要一堆Region凑放在一起然后将这块区域称作新生代,它们之间并不需要连续,所以只有概念上的分代,也是这种分代方式使得G1可以独立管理这个堆空间,不需要跟其他回收器合作。

3、空间整合:G1的算法从Region层面看属于复制算法(从一个Region复制到另一个),但是从整体看又是标记-整理法。然而不管是哪种,都表示G1不会产生内存碎片,不会因为空间不连续放不下大对象而出现FullGC的情况。

G1回收器将内存空间分成若干个Region,并且这些Region之前相互独立。但是我们都知道这并不能真正的独立,因为一个Region中的对象不一定只会被当前Region的其他对象引用,而可能被堆中的其他对象引用,那G1是如何实现避免全堆扫描的呢?这个问题在分代的其他回收器中也有,但是在这里突显得更加明显而已。再G1中,对象本身都会有一个Remembered Set,这个Set存放着当前对象被其他区域对象引用的信息,这样子,在扫描引用的时候加上这个Set就可以避免全堆扫描了。

具体实现大致为:虚拟机在发现程序正在进行对Reference类型的写操作时,会暂时中断写操作,然后检查Reference引用的对象是否处于不同的区域(如果是分代,则只对年老代的对象进行检查,检查是否引用的对象在新生代),如果是的话则将引用信息记录在被引用的Remembered Set中,这样在GC的时候加上Remembered Set的扫描就可以避免全堆扫描了。

跟CMS类型,G1也有四个阶段(不算Remembered Set的扫描),虽然相似但是还是有些区别的。

1、初始标记:标记可达的根节点,STW,单线程,时间短。

2、并发标记:跟用户线程同时执行,并发执行时对象可能会产生引用变化,其会将这些变化记录在Remembered Set Logs中,待下个阶段整合。

3、最终标记:验漏,将并发标记阶段的引用变化记录Remembered Set Logs整合到Remembered Set中。

4、筛选回收:对各个Region中的回收价值进行排序,然后执行回收计划。暂停用户线程,并行执行。

3 小结

本文首先介绍了“对象自救”的方法——finalize,并且用一个小例子演示了对象如何实现自救。接着介绍了7种不同的垃圾回收器,新生代中有单线程的Serial可以作为Client端新生代的默认回收器,有多线程版本的Serial——ParNew,还有着重点不同(吞吐量)的Parallel Scavenge;年老代方面有单线程的Serial Old、跨时代意义的并发回收器——CMS,虽然优秀但不可避免的有三个缺点、还有吞吐量年老代版本——Parallel Old收集器,最后还简单介绍了G1收集器的几个过程还有独立的Region间是如何实现避免堆扫描的。

整体下来整篇行文还有些粗糙,日后会慢慢的圆润,如果有关于这方面好的文章可以在下面评论区分享学习一下,下方为各个垃圾回收器的搭配图。

It helps me a lot if you could share your opinion with us.

java 2分代复制垃圾回收_Java对象的后事处理——垃圾回收(二)相关推荐

  1. java 对象被回收的例子_Java对象的后事处理——垃圾回收(二)

    1 先谈Finalize() finalize()能做的所有工作,使用try-finally或者其他方式都可以做得更好.更及时,所以笔者建议大家完全可以忘掉Java语言中有这个方法的存在. --< ...

  2. 简介三种垃圾回收机制:分代复制垃圾回收,标记垃圾回收,增量垃圾回收

    一.分代复制垃圾回收 不同的对象的生命周期是不一样的.因此,不同生命周期的对象可以采取不同的收集方式,以便提高回收效率. 在Java程序运行的过程中,会产生大量的对象,其中有些对象是与业务信息相关,比 ...

  3. java对象什么时候回收_Java对象的生命周期与垃圾回收以及四种引用

    创建对象的方式用new语句创建对象. 使用反射,调用java.lang.Class或java.lang.reflect.Constructor的newInstance()实例方法. 调用对象的clon ...

  4. JVM学习 谁是垃圾?判断对象是否能被垃圾回收 可达性分析 四大引用

    系统性学习JVM请点击JVM学习目录 为什么要垃圾回收 为什么要进行垃圾回收?做任何事之前我们都要搞清做这件事的原因.当我们在运行java项目时,如果项目比较大,它会实例化很多很多对象,多到我们的内存 ...

  5. java成员变量垃圾回收_JAVA 对象被垃圾回收后其成员变量可用吗? | 学步园

    我们知道JVM中一个对象实例超出作用域或无引用的时候会被垃圾回收,那么他被垃圾回收后,依赖其成员变量的其他实例是否还能正常使用呢?经过测试后发现即使一个实例被垃圾回收,如果他内部的数据被其他对象使用, ...

  6. 计算机网络分代核心的属性,高职单招计算机类模拟试卷二(环职职专)

    计算机高职单招模拟(二) 姓名_______ 座号________成绩___________ 第I卷(选择题共120分) 一.单项选择题(在每小题的四个备选答案中,选出一个正确答案,并将正确答案的字幕 ...

  7. java jdbc(mysql)驱动源码分析_JAVA JDBC(MySQL)驱动源码分析(二)

    本文系转载,地址:http://blog.csdn.net/brilliancezhou/article/details/5425687 上一篇中分析了Class.forName("com. ...

  8. java list按多个字段排序_java 对象List按照多个字段进行排序

    新建一个对象 public class A { String a; String b; public A() { } public A(String a, String b) { this.a = a ...

  9. 【Android 内存优化】内存抖动 ( 垃圾回收算法总结 | 分代收集算法补充 | 内存抖动排查 | 内存抖动操作 | 集合选择 )

    文章目录 一. 垃圾回收算法总结 二. 分代收集算法补充 三. 查看 Java 虚拟机 四. 获取 Android 应用可使用最大内存 五. 内存抖动标志 六. 排查内存抖动 七. 常见的造成内存抖动 ...

最新文章

  1. 巴塞罗那自治大学3D视觉课件
  2. java图形用户界面添加背景颜色不成功的解决方案
  3. iOS唯一标示符引导
  4. iOS架构-自动打包并上传到App Store(python)(21)
  5. 【Kaggle-MNIST之路】CNN+改进过的损失函数+多次的epoch(四)
  6. 哈哈哈,程序员没有女朋友的原因,我终于找到了!
  7. P2834-能力测验【数论,整除分块】
  8. Kicad如何导入封装库、符号库(元件库)以及3D模型文件?
  9. Jumpserver代码规范
  10. 手动开启O2, O3优化
  11. windows权限维持的方法
  12. 按字母顺序排列c语言,C语言有n个国家名,要求按字母先后顺序排列,并按升序输出。(要求:尽量使用标准字符串函数)...
  13. 基于Springboot的漫画之家管理系统
  14. 交通流优化的一种强化学习方法
  15. 《数据结构与算法之哈夫曼树(Java实现)》
  16. Android DEX加壳
  17. 计算机在英语教学,论计算机在英语教学中的辅助作用原稿(电子版)
  18. 华为p40鸿蒙2.0演示,华为P40强硬登场:屏下镜头+鸿蒙2.0+徕卡5摄,国产骄傲绝不服输...
  19. 【学习记录】野火stm32学习记录(一)GPIO
  20. 现在二维码频繁使用,它是唯一的且不会重复吗?哪里负责生成并管理二维码?

热门文章

  1. 前端学习(1714):前端系列javascript之目录结构
  2. java学习(113):Calendar类
  3. java学习(68):局部内部类
  4. FPGA Verilog实现一个脉冲波形变换
  5. gateway请求拦截_一种网关对用户请求进行统一拦截判断是否放行的方法与流程...
  6. 一日一学--如何对数值型特征进行分桶
  7. redis服务器信息统计,利用Redis统计网站在线活跃用户的方法
  8. 计算机双一流a水平大学,教育部公布:全国双一流A类名单出炉,36所大学上榜,你的大学在内吗...
  9. html.parser python_python模块之HTMLParser
  10. python数据标准类型_Python的标准数据类型(下)