一、前言

在前期博文《JVM进阶(五):JAVA GC 之标记》讲到了标记,是不是被标记了就肯定会被回收呢?不知道小伙伴们记不记得Object类有一个finalize()方法,所有类都继承了Object类,因此也默认实现了这个方法。

finalize()方法的用途就是:在该对象被回收之前,该对象的finalize()方法会被调用。这里的回收之前指的就是被标记之后,问题就出在这里,有没有一种情况就是原本一个对象开始不在上一章所讲的“关系网”(引用链)中,但是当开发者重写了finalize()后,并且将该对象重新加入到了“关系网”中,也就是说该对象对我们还有用,不应该被回收,但是已经被标记啦,怎么办呢?

二、二次标记

针对这个问题,虚拟机的做法是进行两次标记,即第一次标记不在“关系网”中的对象。第二次的话就要先判断该对象有没有实现finalize()方法了,如果没有实现就直接判断该对象可回收;如果实现了就会先放在一个队列中,并由虚拟机建立的一个低优先级的线程去执行它,随后就会进行第二次的小规模标记,在这次被标记的对象就会真正的被回收了。我们来看下面的代码:

public class Test {private static Test test = null; // 声明一个类静态变量public static void main(String[] args) throws InterruptedException {test = new Test(); // 生成实例testHelp(); // 第一次调用testHelp(); // 第二次调用}public static void testHelp() throws InterruptedException {test = null; // 将其从关系网中移出// 通知要进行垃圾回收,但不一定会执行垃圾回收,只是一个通知而已// 因此在开发中不要过多的依赖这个方法,这里只是做个测试System.gc();// 等待1s,让低优先级的线程执行完Thread.sleep(1000);if(test === null){System.out.println("我挂啦");} else {System.out.println("我还活着");}}@overrideprotected void finalize() throws Throwable {super.finalize();test = this; // 将其重新加入关系网中System.out.println("我被调用啦");}
}

大家觉得结果会输出什么?最后的结果是:

我被调用啦
我还活着
我挂啦

有木有觉得很诧异,明明调用了两次同样的方法,但输出怎么不同呢?而且明明调用了两次gc()方法(这里确认是执行了gc),那怎么只进入了一次finalize()方法?

嘿嘿,其实面对同一个对象,finalize()方法只会被调用一次,因此第一次调用的时候会进行finalize()方法,并且成功的将该对象加入了“关系网”中,但当第二次回收的时候并不会进入,所以第二次不能将对象加入“关系网”中,导致被回收了。

上面的代码块中有一行让程序睡眠一秒钟的代码,为的就是确保让低优先级的执行finalize()方法线程执行完成。那如果我们把他注释了会怎样呢?输出结果是:

我挂啦
我被调用啦
我挂啦

很奇怪吧,不过如果执行很多次的话,也会出现最开始那样的结果,但多数会是这个结果。因为我们已经说了,执行finalize()的是一个低优先级的线程,既然是一个新的线程,虽然优先级低了点,但也是和垃圾收集器并发执行的,所以垃圾收集器没必要等这个低优先级的线程执行完才继续执行。也就是说,finalize()方法不一定会在对象第一次标记后执行。用一句清晰易懂的话来说就是:虚拟机确实有调用方法的动作,但是不会确保在什么时候执行完成。因此也就出现了上面输出的结果,对象被回收之后,那个低优先级的线程才执行完。

三、拓展阅读

  • 《JVM虚拟机专栏》

JVM进阶(六):鲜为人知的二次标记相关推荐

  1. 【JVM进阶之路】二:Java内存区域

    1.运行时数据区 Java 虚拟机定义了若干种程序运行期间会使用到的运行时数据区,其中有一些会随着虚拟机启动而创建,随着虚拟机退出而销毁.另外一些则是与线程一一对应的,这些与线程对应的数据区域会随着线 ...

  2. JVM进阶(十二)——JAVA 可视化分析工具

    JVM进阶(十二)--JAVA 可视化分析工具   经过前几篇博文对堆内存以及垃圾收集机制的学习,相信小伙伴们已经建立了一套比较完整的理论体系!本篇博客就根据已有的理论知识,通过可视化工具来实践一番. ...

  3. 【JVM进阶之路】垃圾回收机制和GC算法之三色标记(三)

    JVM往期文章 [JVM进阶之路]内存结构(一) [JVM进阶之路]玩转JVM中的对象(二) 上篇文章中讲到JVM中的对象以及判断对象的存活,那么对于"已死"的对象应该如何处理,怎 ...

  4. JVM进阶(八)——Stop The World(停顿类型STW)

    JVM进阶(八)--Stop The World(停顿类型STW)   小伙伴还记得上一篇中我们留下的一个问题吗?什么是停顿类型!经过前几章的学习,我们知道垃圾回收首先是要经过标记的.对象被标记后就会 ...

  5. QIIME2进阶六_QIIME2训练分类器及物种注释

    本文我们主要介绍了如何训练Naive Bayes分类器并把这个分类器应用于扩增子基因序列的物种注释与可视化. 本教程将使用来自人源化(humanized)小鼠的一组粪便样品,展示16S rRNA基因扩 ...

  6. JVM进阶(一):初识 JAVA 栈

    文章目录 一.前言 二.栈存储 三.总结 四.延伸阅读 编译型语言与解释型语言 4.1 编译型语言 4.2 解释型语言 五.拓展阅读 一.前言 若想自己编写的Java程序高效运行,以及进行正确.高效的 ...

  7. 我所理解的JVM(六):内存回收

    2019独角兽企业重金招聘Python工程师标准>>> Java的一大特点就是虚拟机本身拥有垃圾回收机制,用户在编程过程中不再需要考虑垃圾回收的事情.所有的对象的创建都是在堆空间中, ...

  8. html语言标示,HTML语言剖析(二) HTML标记一览

    HTML语言剖析(二) HTML标记一览 2008-04-11 00:00:00 分享到: 摘要 :标记 类型 译名或意义 作 用 备注 文件标记 ● 文件声明 标记 类型 译名或意义 作 用 备注 ...

  9. python进阶06并发之二技术点关键词

    原创博客地址:python进阶06并发之二技术点关键词 GIL,线程锁 python中存在GIL这个"线程锁", 关键地方可以使用c语言解决 GIL问题 然后可以提高cpu占用效率 ...

  10. ILI9341的使用之【六】命令二

    由于ILI9341命令体系比较庞大,因此为了查询方便,把命令部分的解释分为两篇,本文为第二篇.第一篇详细解释了编码在3Fh以下的指令.本文详细解释编码在3Fh以上的指令. <ILI9341的使用 ...

最新文章

  1. 偶然发现静态函数与性能一例
  2. 正则表达式下——相关方法
  3. Oracle闪回详解
  4. .NET 中创建支持集合初始化器的类型
  5. Asp.net2.0工具包AjaxControlToolkit下载和安装
  6. 系统地学习JavaScript
  7. 什么都没学到,记录一个鼠标监听事件吧
  8. 如何用iOS工程生成iOS模拟器包
  9. 【SimuPy】Python实现的Simulink 文档翻译全部完毕
  10. meteor是什么东西?
  11. 蓝桥杯BASIC-28 基础练习 Huffuman树
  12. 安卓miracast花屏_创维酷开电视多屏互动Miracast玩法详解
  13. TDirectory.IsRelativePath是否相对路径
  14. 【全网最详细】 树莓派控制ws2812b灯带 点亮教程
  15. 【高速PCB电路设计】8.DDR模块设计实战
  16. springboot基于Javaweb的超市管理系统毕业设计源码281024
  17. math四舍五入 java_使用Math.cei将Java四舍五入到int
  18. 利用Python对非接触式IC卡的读写操作
  19. 路由器中继模式WISP、Client + AP、AP模式的区别和适使用场景
  20. 电脑进入BIOS界面快捷键是什么

热门文章

  1. linux 创建 swap分区
  2. 大一微积分笔记整理_大一下总结
  3. Flutter 3.0 极光推送
  4. 代码读智识  笔墨知人心
  5. 聚合器是什么东西?聚合器的可能性
  6. 计算机中专生未来三年的规划,职业中专三年发展规划.doc
  7. Android开发之WebDav
  8. 92.发光文字加载特效
  9. 计算机存储一个字节数是,在计算机中,如果一个存储单元能存放一个字节,则容量为64KB的存储器中的存储单元个数 。...
  10. jsp注册页面java代码_使用Servlet和JSP实现用户注册功能