JVM中判断对象是否存活的方法
Java中几乎所有的对象实例都存放在堆中,在垃圾收集器对堆内存进行回收前,第一件事情就是要确定哪些对象还“存活”,哪些对象已经“死去”(即不可能再通过任何途径被使用)。
引用计数算法
首先需要声明,至少主流的Java虚拟机里面都没有选用引用计数算法来管理内存。
什么是引用计数算法:给对象中添加一个引用计数器,每当有一个地方引用它时,计数器值加1;当引用失效时,计数器值减1.任何时刻计数器值为0的对象就是不可能再被使用的。那为什么主流的Java虚拟机里面都没有选用这种算法呢?其中最主要的原因是它很难解决对象之间相互循环引用的问题。在下面的代码中,我们在testGC()中new两个ReferenceCountingGC对象objA和objB,然后令objA.instance=objB,objB.instance=objA,最后令objA和objB都为null。此时,这两个对象实际上已经不可能再被访问,但由于它们互相引用着对方,导致它们的引用计数器值都不为0,于是使用引用计数算法无法通知GC收集器回收它们。
public class ReferenceCountingGC { public Object instance = null; private static final int _1MB = 1024 * 1024; /**
* 这个成员属性的唯一意义就是占点内存,以便在能在GC日志中看清楚是否有回收过
*/
private byte[] bigSize = new byte[2 * _1MB]; public static void testGC() {
ReferenceCountingGC objA = new ReferenceCountingGC();
ReferenceCountingGC objB = new ReferenceCountingGC();
objA.instance = objB;
objB.instance = objA; objA = null;
objB = null; // 假设在这行发生GC,objA和objB是否能被回收?
System.gc();
} public static void main(String[] args) {
testGC();
}
}
现在,我们在main方法中调用testGC(),然后打印GC日志(在Myeclipse中打印GC日志的方法)。得到结果为:
0.161: [GC 4761K->568K(124416K), 0.0022505 secs]
0.163: [Full GC 568K->471K(124416K), 0.0201927 secs]
由“4761K->568K”我们可以得知,虚拟机并没有因为这两个对象相互引用就不回收它们,这也从侧面说明了hotspot虚拟机并不是通过引用计数算法来判断对象是否存活的。
那么,在主流的商用程序语言的主流实现中,是通过什么方法来判断对象是否存活的呢?答案是下面将要介绍的可达性分析算法。
可达性分析算法
可达性分析算法的基本思路是:通过一系列的称为“GC Roots”的对象作为起始点,从这些节点考试向下探索,搜索所走过的路径称为“引用链”,当一个对象到GC Roots没有任何引用链相连(用图论的话来说,就是从GC Roots到这个对象不可达)时,则证明此对象是不可用的。如下图中,对象object5、object6、object7虽然互有关联,但是它们到GC Roots是不可达的,所以它们将会被判定为是可回收的对象。
在Java语言中,可作为GC Roots的对象包括下面几种:
1. 虚拟机栈(栈帧中的本地变量表)中引用的对象;
2. 方法区中类静态属性引用的对象;
3. 方法区中常量引用的对象;
4. 本地方法栈中JNI(即一般说的Native方法)引用的对象。
实例分析
参考:https://www.zhihu.com/question/21539353/answer/95667088垃圾回收机制中,引用计数法是如何维护所有对象引用的? - Gityuan的回答 - 知乎 https://www.zhihu.com/question/21539353/answer/95667088
以一段代码为例,从内存角度解释以上两种算法。
public class GcDemo { public static void main(String[] args) {
//分为6个步骤
GcObject obj1 = new GcObject(); //Step 1
GcObject obj2 = new GcObject(); //Step 2 obj1.instance = obj2; //Step 3
obj2.instance = obj1; //Step 4 obj1 = null; //Step 5
obj2 = null; //Step 6
}
} class GcObject{
public Object instance = null;
}
情况(一):引用计数算法
如果采用的是引用计数算法:
再回到前面代码GcDemo的main方法共分为6个步骤:
- Step1:GcObject实例1的引用计数加1,实例1的引用计数=1;
- Step2:GcObject实例2的引用计数加1,实例2的引用计数=1;
- Step3:GcObject实例2的引用计数再加1,实例2的引用计数=2;
- Step4:GcObject实例1的引用计数再加1,实例1的引用计数=2;
执行到Step 4,则GcObject实例1和实例2的引用计数都等于2。
接下来继续结果图:
- Step5:栈帧中obj1不再指向Java堆,GcObject实例1的引用计数减1,结果为1;
- Step6:栈帧中obj2不再指向Java堆,GcObject实例2的引用计数减1,结果为1。
到此,发现GcObject实例1和实例2的计数引用都不为0,那么如果采用的引用计数算法的话,那么这两个实例所占的内存将得不到释放,这便产生了内存泄露。
情况(二):可达性算法
这是目前主流的虚拟机都是采用GC Roots Tracing算法,比如Sun的Hotspot虚拟机便是采用该算法。 该算法的核心算法是从GC Roots对象作为起始点,利用数学中图论知识,图中可达对象便是存活对象,而不可达对象则是需要回收的垃圾内存。这里涉及两个概念,一是GC Roots,一是可达性。
那么可以作为GC Roots的对象(见下图):
- 虚拟机栈的栈帧的局部变量表所引用的对象;
- 本地方法栈的JNI所引用的对象;
- 方法区的静态变量和常量所引用的对象;
关于可达性的对象,便是能与GC Roots构成连通图的对象,如下图:
从上图,reference1、reference2、reference3都是GC Roots,可以看出:
- reference1-> 对象实例1;
- reference2-> 对象实例2;
- reference3-> 对象实例4;
- reference3-> 对象实例4 -> 对象实例6;
可以得出对象实例1、2、4、6都具有GC Roots可达性,也就是存活对象,不能被GC回收的对象。
而对于对象实例3、5直接虽然连通,但并没有任何一个GC Roots与之相连,这便是GC Roots不可达的对象,这就是GC需要回收的垃圾对象。
到这里,相信大家应该能彻底明白引用计数算法和可达性算法的区别吧。
再回过头来看看最前面的实例,GcObject实例1和实例2虽然从引用计数虽然都不为0,但从可达性算法来看,都是GC Roots不可达的对象。
总之,对于对象之间循环引用的情况,引用计数算法,则GC无法回收这两个对象,而可达性算法则可以正确回收。
JVM中判断对象是否存活的方法的更多相关文章
- JavaScript中判断对象类型的种种方法
我们知道,JavaScript中检测对象类型的运算符有:typeof.instanceof,还有对象的constructor属性: 1) typeof 运算符 typeof 是一元运算符,返回结果是一 ...
- 转 JavaScript中判断对象类型的种种方法
我们知道,JavaScript中检测对象类型的运算符有:typeof.instanceof,还有对象的constructor属性: 1) typeof 运算符 typeof 是一元运算符,返回结果是一 ...
- JS中判断对象是不是数组的方法
JavaScript中检测对象的方法 1.typeof操作符 这种方法对于一些常用的类型来说那算是毫无压力,比如Function.String.Number.Undefined等,但是要是检测Arra ...
- JavaScript中判断对象是否属于Array类型的4种方法及其背后的原理与局限性
前言 毫无疑问,Array.isArray是现如今JavaScript中判断对象是否属于Array类型的首选,但是我认为了解本文其余的方法及其背后的原理与局限性也是很有必要的,因为在JavaScrip ...
- JVM 中的对象及引用
JVM中对象的创建过程 对象的内存分配 虚拟机遇到一条 new 指令时,首先检查是否被类加载器加载,如果没有,那必须先执行相应的类加载过程. 类加载就是把 class 加载到 JVM 的运行时数据区的 ...
- 3、JVM中的对象
1.对象的创建 A a = new A() A:引用的类型 a::引用的名称 new A():创建一个A类对象 当创建一个对象时,具体创建过程是什么呢? (1)JVM遇到new的字节码指令后,检查类 ...
- java虚拟机判断对象是否存活的方式
引用计数算法: 给对象添加一个引用计数器,每当有地方应用时,计数器值就加一,当引用失效时,程序计数器就减一,只要引用计数器的值为零时,就表示对象不可能再被引用,例如微软的 component ob ...
- js中判断对象具体类型
大家可能知道js中判断对象类型可以用typeof来判断.看下面的情况 <script> alert(typeof 1);//number alert(typeof "2" ...
- js中判断对象是否存在
s中判断对象是否存在,写法有很多种: 第一种:if (!myObj) { var myObj = { }; }第二种:var global = this; if (!global.myObj) { ...
随机推荐
- 关于读取Sql Server数据库时间前端处理问题
var time = this.CreateTime; this.CreateTime = new Date(time.replace("T", " ")).F ...
- python 基础 set 集合类型补充
为啥今天又重提这个数据类型呢?平时用的少,等要用起来的时候才发现,自己对这块啥都不知道了,so,今天就把这块再梳理一下咯. 一.set集合,是一个无序且不重复的元素集合.这一点是非常重要的. 二.集合 ...
- float和position
float float是欺骗父元素,让其父元素误以为其高度塌陷了,但float元素本身仍处于文档流中,文字会环绕着float元素,不会被遮蔽. absolute 但absolute其实已经不能算是欺骗 ...
- linux下git常用命令
1 安装: sudo apt-get install git 2 创建一个版本库: 新建一个文件夹,进入这个目录之后 git init 3 创建一个版本: git add 文件名或目录 #表示将当前文 ...
- Python之进程
进程 进程(Process)是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础.在早期面向进程设计的计算机结构中,进程是程序的基本执行实体:在当代 ...
- Web 项目报错No suitable driver found for jdbc:mysql://localhost:3306/book 的一个解决办法
确认jar包加入到了build path中,然后注意版本是否与数据库相配,还要留意将jar包放入WEB-INF下的lib文件夹中
- Git撤销commit消息保留修改
有时候commit后发现commit信息错了或者是添加了不想commit的内容,但还没有push到远程仓库 这个时候 git reset --soft [commit_id] 就可以回滚到某一个com ...
- POJ-1328 Radar Installation--区间选点问题(贪心)
题目链接: https://vjudge.net/problem/POJ-1328 题目大意: 假设陆地的海岸线是一条无限延长的直线,海岛是一个个的点,现需要在海岸线上安装雷达,使整个雷达系统能够覆盖 ...
- U盘分区后合并
1.首先把你的U盘插在电脑上,点击开始→所有程序→附件→命令提示符(前面这步骤也可以直接用Win+R组合键打开"运行",之后输入cmd也可打开命令提示符). 2.然后在命令提示窗口 ...
- JavaScript初探之字符串与数组
一直在研究JS以至于忘记跟新博客... 字符串:// str.charAt(x); //获取下标为x的字符// str.indexOf(",",1); //获取",&qu ...