深入理解JVM(③)判断对象是否还健在?
前言
因为Java对象主要存放在Java堆里,所以垃圾收集器(Garbage Collection)在对Java堆进行回收前,第一件事情就是要确定这些对象之中哪些还“存活”着,哪些已经“死去”(不被引用了)。
判断对象是否健在的算法
1.引用计数算法
引用计数算法,很容易理解,在对象中添加一个引用计数器,每有一个地方引用它时,计数器值就加一;当引用失效是,计数器值就减一;任何时刻计数器为零的对象就是不可以能再被使用的对象。
引用计数算法的原理简单,判定效率也很高。市面上也确实有一些技术使用的此类算法来判定对象是否存活,像ActionScript 3 的FlashPlayer、Python语言等。但是在主流的Java虚拟机里面都没有选用引用计算法来管理内存,主要是使用此算法时,必须要配合大量的额外处理才能保证正确的工作,例如要解决对象之间的相互循环引用的问题。
public class OneTest {
public Object oneTest = null;
private static final int _1MB = 1024 * 1024;
private byte[] bigSize = new byte[256 * _1MB];
/**
* 这个成员属性的唯一意义就是占点内存,以便能在GC日志中看清楚是否有回收过。
*/
@Test
public void testGC(){
OneTest test1 = new OneTest();
OneTest test2 = new OneTest();
test1.oneTest = test2;
test2.oneTest = test1;
test1 = null;
test2 = null;
// 假设在这行发生GC,test1和test2是否能被回收?
System.gc();
}
}
分析代码,test1和test2对象都被设置成了null,在后面发生GC的时候,如果按照引用计数算法,这两个对象虽然都被设置成了null,但是test1引用了test2,test2又引用了test1,所以这两个对象的引用计数值都不为0,所以都不会被回收,但是真正的实际运行结果是,这两个对象都被回收了,这也说明HotSpot虚拟机并不是用引用计数法来进行的内存管理。
2. 可达性分析算法
当前主流的商用程序语言(Java、C#等),都是通过可达性分析(Reachability Analysis)算法来判断对象是否存活的。这个算法的基本思路就是通过一一系列称为“GC Roots” 的根对象作为起始节点集,从这些节点开始根据引用关系向下搜索,搜索走过的的路径称为“引用链”(Reference Chain),如果某个对象到GC Roots 间没有任何引用链相连,或者从GC Roots 到这个对象不可达时,则证明此对象是不可能再被使用的。
如下图,object10、object11、object12这三个对象,虽然互相有关联,但是它们到GC Roots是不可达的,因此它们会被判定为可回收的对象。
在Java程序中,固定可作为GC Roots 的对象包括以下几种:
- 在虚拟机栈(栈帧中的本地变量表)中引用的对象,譬如各个现场被调用的方法堆栈中使用到的参数、局部变量、临时变量等。
- 在方法区中类静态属性引用的对象,譬如Java类的引用类型静态变量。
- 在方法区中常量引用的对象,譬如字符串常量池(String Table)里的引用。
- 在本地方法栈中JNI(即通常所说的Native方法)引用的对象。
- Java虚拟机内部的引用,如基本数据类型对应的Class对象,一些常驻的异常对象(NullPointException、OutOfMemoryError)等,还有系统类加载器。
- 所有被同步锁(synchronized关键字)持有的对象。
- 反映Java虚拟机内部情况的JMXBean、JVMTI中注册的回调、本地代码缓存等。
除了这些固定的GC Roots集合以外,根据垃圾收集器以及当前回收的呢村区域不同,还会有其他对象“临时性”的加入,如果只针对Java堆中某一块儿区域发起垃圾收集时(例如只针对年轻代的垃圾收集),必须考虑到当前区域内的对象是否有被其他区域的对象所引用,这个时候就需要把这些关联区域的对象一并加入GC Roots集合中,来保证可达性分析的正确性。
重申引用
无论是通过引用计数算法判断对象的引用数量,还是通过可达性分析算法判断对象是否引用链可达,判断对象是否存活都和“引用”离不开关系。在JDK1.2之前,Java里对引用的概念是:如果reference类型的数据中存储的数值代表的是另外一块儿内存的地址,就称该reference数据是代表某块内存、某个对象的引用。
在JDK1.2版之后,Java对引用的概念进行了扩充,将引用分为强引用(Strongly Reference)、软引用(Soft Reference)、弱引用(Weak Reference)、虚引用(Phantom Reference)4种,这4种引用强度依次逐渐减弱。
- 强引用是最传统的“引用”的定义,指引用复制,即类似
Object obj = new Object()
这种引用关系。无论在任何情况下,只要强引用关系还存在,垃圾收集器就不会回收掉被引用的对象。
- 软引用是用来描述一些还有用,但非必须的对象。在系统发生内存溢出前,会先对软引用对象进行第二次回收,如果回收后还没有足够的内存,才会抛出内存溢出的异常。
- 弱引用也是用来描述那些非必须的对象,但是它的强度比软引用更弱一些,弱引用的对象,只能生存到下一次垃圾收集发生为止。当垃圾收集器开始工作,无论当前内存是否足够,都会回收掉只被弱引用关联的对象。
- 虚引用也称为“幽灵引用”或“幻影引用”,它是最弱的一种引用关系。为一个对象设置虚引用关联的唯一目的只是为了能在这个对象被收集器回收时收到一个系统通知。
判断对象是生是死的过程
即使在可达性分析算法中,判断为不可达的对象,也不是“非死不可”的,要真正宣告一个对象死亡,至少要经历两次标记过程:
- 如果第一次对象在进行可达性分析后发现与GC Roots 不可达,将进行第一次标记。
- 随后对此对象进行一次是否有必要执行finalize()方法进行筛选,假如对象没有覆盖finalize()方法,或者finalize()方法已经被虚拟机调用过,都视为“没有必要执行”。
如果对象被判定有必要执行finalize()方法,会将对象放置在一个名为F-Queue的队列中,并在由一条由虚拟机自动建立的、低调度的线程区执行它们的finalize()方法。但并不承诺一定会等待它们运行结束。
需要注意的是:任何一个对象的finalize()方法都只会被系统自动调用一次,如果对象面临第二次回收,它的finalize()方法不会被再次执行。
还有一点就是Java官方已经明确声明不推荐手动调用finalize()方法了,因为它的运行代价高昂,不确定性大,无法保证各个对象的调用顺序,并且finanlize()能做的所有工作,使用try-finally或其他方式都可以做的更好、更及时。
回收方法区
方法区垃圾收集的“性价比”通常比较低,并且方法区回收也有过于苛刻的判定条件。
方法区的垃圾收集主要回收两部分内容:废弃的常量和不再使用的类型,回收废弃常量时,如果当前系统没有一个常量的值是当前常量值,且虚拟机中也没有其他地方引用这个常量。如果这个时候发生垃圾回收,常量就会被系统清理出常量池。
判定一个类型是否属于“不再使用的类”的条件就比较苛刻了,要同时满足如下三个条件:
- 该类所有的实例都已经被回收,也就是Java堆中不存在该类及其任何派生子类的实例。
- 加载该类的类加载器已经被回收,这个条件除非是经过精心设计的可替换类加载器的场景,如OSGi、JSP的冲加载等,否则通常很难达成的。
- 该类对应的java.lang.Class对象没有在任何地方被引用,无法在任何地方通过反射访问该类的方法。
同时满足了上述的三个条件后,也只是被允许进行回收了,关于是否要对类型进行回收还要对虚拟机进行一系列的参数设置,这里就不赘述了,感兴趣的可以自己去查询。
深入理解JVM(③)判断对象是否还健在?的更多相关文章
- JVM 判断对象已死,实践验证GC回收
作者:小傅哥 博客:https://bugstack.cn 沉淀.分享.成长,让自己和他人都能有所收获! 一.前言 提升自身价值有多重要? 经过了风风雨雨,看过了男男女女.时间经过的岁月就没有永恒不变 ...
- 深入理解JVM(三) -- 对象的内存布局和访问定位
一 对象的内存布局: 在HotSpot虚拟机中,对象在内存中存储的布局可以分为3块区域:对象头(Header),实例数据(Instance Data)和对齐填充(Padding). HotSpot的对 ...
- [jvm] -- 判断对象是否死亡篇
判断对象是否死亡的两种方法 引用计数法 给对象中添加一个引用计数器,每当有一个地方引用它,计数器就加 1:当引用失效,计数器就减 1:任何时候计数器为 0 的对象就是不可能再被使用的. 优点: 简单 ...
- 理解JVM之对象的生命周期
1.对象的创建 1) 当虚拟机遇到一条new的指令时,首先将去检查这个指令的参数是否能在常量池中定位到一个类的符号引用,并且检查这个符号引用代表的类是否已被加载,解析和初始化过. 2) 在类加载检查通 ...
- 深入理解JVM(二)--对象的创建
Java是一门面向对象的语言,在Java程序运行的过程中,无时无刻都会有对象被创建出来,在程序语言中,创建对象(例如克隆,反序列化)通常仅仅是一个new关键字,但是在虚拟机中是怎样的呢?本文主要了解一 ...
- JVM——判断对象的死活
一.引用计数法 给对象中添加一个引用计数器,每当有一个地方引用它时,计数器值就加1,当引用失效时,计数器值就减1,任何时刻计数器为0的对象就是不可能再被使用的. 但是它很难解决对象之间相互循环引用的问 ...
- 深入理解JVM(4)——对象的创建和访问
1.对象的创建 在语言层面上,创建对象(例如克隆,反序列化)通常仅仅是一个new关键字而已. 在虚拟机中,对象(文中讨论的对象限于普通 Java 对象,不包括数组和 Class 对象等)的创建过程如下 ...
- 2.1.JVM的垃圾回收机制,判断对象是否死亡
因为热爱,所以坚持. 文章下方有本文参考电子书和视频的下载地址哦~ 这节我们主要讲垃圾收集的一些基本概念,先了解垃圾收集是什么.然后触发条件是什么.最后虚拟机如何判断对象是否死亡. 一.前言 我们 ...
- JVM(二) 对象存活判断和垃圾回收算法
对象的创建 概述 下面简要介绍创建对象的几个重要步骤 : 检查能否在常量池定位到一个类的符号引用,并检查这个符号代表的类是否已被加载,解析和初始化过.如果没有则执行类加载的操作.(即是说对象的引用放在 ...
随机推荐
- Python内置函数示例
abs() 返回数字绝对值 >>> abs(-100) 100 >>> abs(10) 10 >>> all() 判断给定的可迭代参数 itera ...
- Spring AOP实现接口验签
因项目需要与外部对接,为保证接口的安全性需要使用aop进行方法的验签; 在调用方法的时候,校验外部传入的参数进行验证, 验证通过就执行被调用的方法,验证失败返回错误信息: 不是所有的方法都需要进行验签 ...
- 微信小程序-视频弹幕的项目
1.视频播放器 2.选择弹幕颜色 3.弹幕来了... 一般微信小程序需要配置.wxml.wxss.js.json文件,所有接下来也是要配置这几个文件,请看下图: 第一: index.wxml < ...
- linux常用命令---文件权限操作
文件权限
- form组件注册ajax登录auth认证及验证码
本项目采用django自带的数据库 项目文件 models.py from django.db import models from django.contrib.auth.models import ...
- 【解决办法】IIS环境中,打开网站后就直接列出了所有文件
有时候访问一个不应当被访问的网站目录时网站会列出该目录下的所有文件,这很不安全,尤其是我们希望我们自己的网站不出现这种情况,如下图所示. 解决办法,在网站根目录新建web.config文件,内容如下: ...
- css不换行解决
word-wrap: break-word; word-break: break-all; white-space: pre-wrap;
- C#判断TCP连接状态
- Linux操作系统分析 | 深入理解系统调用
实验要求 1.找一个系统调用,系统调用号为学号最后2位相同的系统调用 2.通过汇编指令触发该系统调用 3.通过gdb跟踪该系统调用的内核处理过程 4.重点阅读分析系统调用入口的保存现场.恢复现场和系统 ...
- C#网络编程入门之TCP
目录: C#网络编程入门系列包括三篇文章: (一)C#网络编程入门之UDP (二)C#网络编程入门之TCP (三)C#网络编程入门之HTTP 一.概述 UDP和TCP是网络通讯常用的两个传输协议,C# ...