JVM进阶(六):鲜为人知的二次标记
一、前言
在前期博文《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进阶(六):鲜为人知的二次标记相关推荐
- 【JVM进阶之路】二:Java内存区域
1.运行时数据区 Java 虚拟机定义了若干种程序运行期间会使用到的运行时数据区,其中有一些会随着虚拟机启动而创建,随着虚拟机退出而销毁.另外一些则是与线程一一对应的,这些与线程对应的数据区域会随着线 ...
- JVM进阶(十二)——JAVA 可视化分析工具
JVM进阶(十二)--JAVA 可视化分析工具 经过前几篇博文对堆内存以及垃圾收集机制的学习,相信小伙伴们已经建立了一套比较完整的理论体系!本篇博客就根据已有的理论知识,通过可视化工具来实践一番. ...
- 【JVM进阶之路】垃圾回收机制和GC算法之三色标记(三)
JVM往期文章 [JVM进阶之路]内存结构(一) [JVM进阶之路]玩转JVM中的对象(二) 上篇文章中讲到JVM中的对象以及判断对象的存活,那么对于"已死"的对象应该如何处理,怎 ...
- JVM进阶(八)——Stop The World(停顿类型STW)
JVM进阶(八)--Stop The World(停顿类型STW) 小伙伴还记得上一篇中我们留下的一个问题吗?什么是停顿类型!经过前几章的学习,我们知道垃圾回收首先是要经过标记的.对象被标记后就会 ...
- QIIME2进阶六_QIIME2训练分类器及物种注释
本文我们主要介绍了如何训练Naive Bayes分类器并把这个分类器应用于扩增子基因序列的物种注释与可视化. 本教程将使用来自人源化(humanized)小鼠的一组粪便样品,展示16S rRNA基因扩 ...
- JVM进阶(一):初识 JAVA 栈
文章目录 一.前言 二.栈存储 三.总结 四.延伸阅读 编译型语言与解释型语言 4.1 编译型语言 4.2 解释型语言 五.拓展阅读 一.前言 若想自己编写的Java程序高效运行,以及进行正确.高效的 ...
- 我所理解的JVM(六):内存回收
2019独角兽企业重金招聘Python工程师标准>>> Java的一大特点就是虚拟机本身拥有垃圾回收机制,用户在编程过程中不再需要考虑垃圾回收的事情.所有的对象的创建都是在堆空间中, ...
- html语言标示,HTML语言剖析(二) HTML标记一览
HTML语言剖析(二) HTML标记一览 2008-04-11 00:00:00 分享到: 摘要 :标记 类型 译名或意义 作 用 备注 文件标记 ● 文件声明 标记 类型 译名或意义 作 用 备注 ...
- python进阶06并发之二技术点关键词
原创博客地址:python进阶06并发之二技术点关键词 GIL,线程锁 python中存在GIL这个"线程锁", 关键地方可以使用c语言解决 GIL问题 然后可以提高cpu占用效率 ...
- ILI9341的使用之【六】命令二
由于ILI9341命令体系比较庞大,因此为了查询方便,把命令部分的解释分为两篇,本文为第二篇.第一篇详细解释了编码在3Fh以下的指令.本文详细解释编码在3Fh以上的指令. <ILI9341的使用 ...
最新文章
- 偶然发现静态函数与性能一例
- 正则表达式下——相关方法
- Oracle闪回详解
- .NET 中创建支持集合初始化器的类型
- Asp.net2.0工具包AjaxControlToolkit下载和安装
- 系统地学习JavaScript
- 什么都没学到,记录一个鼠标监听事件吧
- 如何用iOS工程生成iOS模拟器包
- 【SimuPy】Python实现的Simulink 文档翻译全部完毕
- meteor是什么东西?
- 蓝桥杯BASIC-28 基础练习 Huffuman树
- 安卓miracast花屏_创维酷开电视多屏互动Miracast玩法详解
- TDirectory.IsRelativePath是否相对路径
- 【全网最详细】 树莓派控制ws2812b灯带 点亮教程
- 【高速PCB电路设计】8.DDR模块设计实战
- springboot基于Javaweb的超市管理系统毕业设计源码281024
- math四舍五入 java_使用Math.cei将Java四舍五入到int
- 利用Python对非接触式IC卡的读写操作
- 路由器中继模式WISP、Client + AP、AP模式的区别和适使用场景
- 电脑进入BIOS界面快捷键是什么