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中判断对象是否存活的方法的更多相关文章

  1. JavaScript中判断对象类型的种种方法

    我们知道,JavaScript中检测对象类型的运算符有:typeof.instanceof,还有对象的constructor属性: 1) typeof 运算符 typeof 是一元运算符,返回结果是一 ...

  2. 转 JavaScript中判断对象类型的种种方法

    我们知道,JavaScript中检测对象类型的运算符有:typeof.instanceof,还有对象的constructor属性: 1) typeof 运算符 typeof 是一元运算符,返回结果是一 ...

  3. JS中判断对象是不是数组的方法

    JavaScript中检测对象的方法 1.typeof操作符 这种方法对于一些常用的类型来说那算是毫无压力,比如Function.String.Number.Undefined等,但是要是检测Arra ...

  4. JavaScript中判断对象是否属于Array类型的4种方法及其背后的原理与局限性

    前言 毫无疑问,Array.isArray是现如今JavaScript中判断对象是否属于Array类型的首选,但是我认为了解本文其余的方法及其背后的原理与局限性也是很有必要的,因为在JavaScrip ...

  5. JVM 中的对象及引用

    JVM中对象的创建过程 对象的内存分配 虚拟机遇到一条 new 指令时,首先检查是否被类加载器加载,如果没有,那必须先执行相应的类加载过程. 类加载就是把 class 加载到 JVM 的运行时数据区的 ...

  6. 3、JVM中的对象

    1.对象的创建 A  a = new A() A:引用的类型 a::引用的名称 new A():创建一个A类对象 当创建一个对象时,具体创建过程是什么呢? (1)JVM遇到new的字节码指令后,检查类 ...

  7. java虚拟机判断对象是否存活的方式

    引用计数算法:   给对象添加一个引用计数器,每当有地方应用时,计数器值就加一,当引用失效时,程序计数器就减一,只要引用计数器的值为零时,就表示对象不可能再被引用,例如微软的 component ob ...

  8. js中判断对象具体类型

    大家可能知道js中判断对象类型可以用typeof来判断.看下面的情况 <script> alert(typeof 1);//number alert(typeof "2" ...

  9. js中判断对象是否存在

    s中判断对象是否存在,写法有很多种: 第一种:if (!myObj) { var myObj = { }; }第二种:var global = this;  if (!global.myObj) {  ...

随机推荐

  1. MySQL binlog 日志

    一:MySQL 日志的三种类型: statement.row.mix 格式.推荐使用row格式. 怎么设置自己的日志格式呢? 1. set globle binlog_format='MIXED' 2 ...

  2. SpringCloud的Hystrix(一) 一个消费者内的两个服务监控

    一.概念与定义 1.服务雪崩 在微服务架构中,整个系统按业务拆分出一个个服务,这些服务之间可以相互调用(RPC),为了保证服务的高可用,单个服务通常会集群部署. 但是由于网络原因或自身原因,服务并不能 ...

  3. Linux下的Shell编程(2)环境变量和局部变量

    Shell Script是一种弱类型语言,使用变量的时候无需首先声明其类型. 局部变量在本地数据区分配内存进行存储,这个变量归当前的Shell所有,任何子进 程都不能访问本地变量.这些变量与环境变量不 ...

  4. 敏捷项目需求拆解&发现用户故事

    需求文档和敏捷中的Epic,User Story, Task之间是什么关系以及如何将需求文档转换成敏捷方式的描述,指导开发人员. 一直是很多公司团队比较困扰的问题,那么最近笔者为了解决这些问题,上了一 ...

  5. 角落的开发工具集之Vs(Visual Studio)2017插件推荐

    因为最近录制视频的缘故,很多朋友都在QQ群留言,或者微信公众号私信我,问我一些工具和一些插件啊,怎么使用的啊?那么今天我忙里偷闲整理一下清单,然后在这里面公布出来. Visual Studio 201 ...

  6. python/数据库操作补充—模板—Session

    python/数据库操作补充—模板—Session 一.创建一个app目录 在models.py只能类进行进行创建表 class Foo: xx= 字段(数据库数据类型) 字段类型 字符串 Email ...

  7. Hibernate(二):MySQL server version for the right syntax to use near 'type=InnoDB' at line x

    目前使用的hibernate5.2.9版本,配置的mysql方言为: <property name="hibernate.dialect">org.hibernate. ...

  8. Selenium+Chrome/phantomJS模拟浏览器爬取淘宝商品信息

    #使用selenium+Carome/phantomJS模拟浏览器爬取淘宝商品信息 # 思路: # 第一步:利用selenium驱动浏览器,搜索商品信息,得到商品列表 # 第二步:分析商品页数,驱动浏 ...

  9. Java基础详解

    从写Java系列的第一篇到现在已经三个月了,因为在网络上或书籍中没有见到一些很适合初学者的学习流程,所以下决心自己写一写,也当作回顾一下Java的知识.网上有许多Java教程之类的内容,都是从概念起步 ...

  10. ng-select 下拉的两种方式

    <!doctype html><html lang="en"><head> <meta charset="UTF-8" ...