JVM如何判断对象能否被回收

  •写在前面

  说起Java和C++,很容易想到让人疯狂的指针,Java使用了内存动态分配和垃圾回收技术,让我们从C++的各种指针问题中摆脱出来,更加专心于业务逻辑,不过如果我们需要深入了解java的JVM相关原理,我们必须要面对这些东西,深入了解JVM在内存动态分配和垃圾回收技术的原理知识,这篇文章就是来做一个先导,在jvm进行垃圾回收之前,它必须要知道回收的对象是否已“死”,这样才能保证程序的正常稳定。

  •对象的创建

  我们将回收对象前,先讲讲在虚拟机上,对象是怎么被创建的。在我们编写代码的角度(语言层面)来看,我们创建一个对象实例,只需要使用new关键词就完事儿了,很简单,不过你享受的简单是因为虚拟机帮你承受了所有繁琐的工作,那虚拟机是怎么工作创建一个对象的呢?

  当虚拟机遇到new指令时,首先会去检查这个指令的参数是否能在常量池中定位到一个类的符号引用(没有类,创建个锤子的对象),并且检查这个符号引用代表的类是否已被加载、解析和初始化过,如果没有,那必须要执行相应的类加载过程。这是第一步,在类加载检查过后,接下来虚拟机将为新生对象分配内存,对象所需的内存大小在类加载完成后便已经完全确定了(这里插一句,如何确定的?这就和对象的内存布局有关了,对象在内存中的布局可以分为3个区域,分别是对象头、实例数据和对齐填充,对象头里面存的是对象自身的运行时数据,比如哈希码、GC分代年龄、锁状态、线程持有的锁、偏向线程ID等等之类的信息,也就是和储存数据无关的额外内存空间,按道理这一块空间应该是固定的,不过在设计上还是被弄成了非固定的数据结构,这样更具不同的类节省空间,不深入不然扯不完,想要可以看另外一篇文章。接下来实例数据就是对象真正储存的有效信息,也是程序代码中所定义的各种类型的字段内容。最后一个对齐填充,顾名思义就是填补空间,因为以HotSpotVM为例,对象的大小必须是8字节的整数倍,所以就靠这个补全),给对象分配空间的任务相当于把一块确定大小的内存从Java堆中划分出来(为啥可以看我另一篇文章,运行时数据区)。

  划分的时候会出现两种情况,第一种就是java堆中的内存是绝对规整的,所有用过的内存都放在一边,空闲的内存放在另一边,中间放着一个指针作为分界点的指示器,那所分配内存就仅仅是把那个指针向空闲空间的一边移动对象大小相等的距离,这种分配方式就是“指针碰撞”。第二种情况就是空间不规整,也就是已使用的内存和空闲内存相互交错,这个时候指针碰撞起不来作用,那么这个时候虚拟机必须维护一个列表,记录哪些内存可用,在分配的时候从列表中找到一块足够大的空间划分给对象实例,并更新相关内存信息,这种方式叫做“空闲列表”。因为创建对象非常频繁,所以会涉及到并发的时候,会出现一个叫做“本地线程分配缓冲”的概念,我这里也不深入,自己去查,哈哈哈。空间分配完成之后,虚拟机需要分配到的内存空间都进行初始化为零值(注意不包括对象头),这样就保证对象的实例字段在java代码中可以不赋初始值就直接使用。最后虚拟机要对对象进行必要的设置,例如这个对象是哪个类的实例、如何才能找到类的元数据信息,对象的哈希码、对象的GC分代等信息。到此,对于虚拟机来说,对象创建完毕。

  •引用计数算法

  引用计数是一个很好理解的概念,就是给对象添加一个引用计数器,每当有一个地方引用这个对象时,计数器值加1,每当一个引用失效时,计数器减1,任何时刻计数器为0的对象就是不可能再被使用的。是不是很好理解,而且判定对象是否可用效率很高,在大部分时候它是一个很不错的算法,不过要注意,是大部分时候。在java虚拟机中,并没有使用这个算法来管理内存,其中最主要的原因就是它很难解决对象之间循环引用的问题。来,举个例子来理解,比如现在有两个对象objectA和objectB都有字段instance,赋值让objectA.instance = objectB, objectB.instance = objectA,除此之外没有任何其他引用,实际上这两个对象已经不可能再被访问了,但是因为它们两个互相引用这对方,导致它们的引用计数不为0,则算法不能通知GC收集器回收它们。所以这种算法不适合在虚拟机上使用,但是并不是说这个算法很垃圾,它可是在其他方面有很多著名的案例。

  •可达性分析算法

  JVM的主流实现是可达性分析,可达性分析在概念上其实也不难理解,它的基本思路就是通过一系列的称为“GC Roots”的对象作为起始点,从这些节点开始向下搜索,搜索所走过的路径称为引用链,当一个对象到GC Roots没有任何引用链相连时(图论里面专业一点来说,就是从GC Roots到这个对象不可达),则证明对象是不可用的,大致可以像下图理解。

  那么哪些对象可以作为GC Roots对象呢?在java中大致有如下几种:

  •虚拟机栈(栈帧中的本地变量表)中引用的对象;(不知道栈帧是啥的看我另一篇文章,运行时数据区)

  •方法区中类静态属性引用的对象;

  •方法区常量引用的对象;

  •本地方法栈中JNI(即一般说的Native方法)引用的对象;

  •引用郑州治疗妇科的医院 http://www.120kdfk.com/

  引用是啥?搞过C++的我们第一反应就会回答,如果reference类型的数据中储存的数值代表的是另一个内存的起始地址,就称这块内存代表着一个引用。这种定义没有错,不过太狭隘了,一个对象在这种定义下只能被引用或者没有被引用两种状态,显然在回收中不足以应付碰到的情况。所以,java对引用概念进行了扩充,将引用分为强引用、软引用、弱引用、虚引用四种,这四种引用强度一次逐渐减弱。

  •强引用,就是指在程序代码之中普遍存在的,类似A a = new A()这样的引用,只要强引用存在,垃圾回收器就不会回收掉被引用的对象;

  •软引用,用来描述一些还有用但并非必须的对象,对于软引用的对象,在系统将要发生内存溢出异常之前,将会把这些对象列进回收范围之中进行第二次回收。如果这次回收还没有足够的内存,才会出现内存溢出异常;

  •弱引用,也是用来描述非必需的对象,但是它的强度比软引用更弱,被弱引用关联的对象只能生存到下一次回收发生之前,当垃圾回收器工作时,无论当前内存是否足够,都会回收掉;

  •虚引用,它是最弱的一种引用关系,一个对象是否有虚引用的存在,完全不会对其生存时间构成影响,也无法通过虚引用取得一个对象实例、为一个对象设置引用关联的唯一目的就是能在这个对象被收集器回收时收到一个系统通知。

  •不可达必须“死”?

  其实在实际中,就算在可达性分析算法中不可达的对象,也并非一定会回收,这个时候不可达的对象暂时处于暂缓的阶段,一个对象要真正宣告死亡,至少要经历两次标记的过程,当对象进行可达性分析而不可达时,它会被第一次标记并且进行一次筛选,筛选条件是这个对象是否有必要执行finalize()方法。当对象没有覆盖finalize()方法,或者finalize()方法已经被调用过了,虚拟机将会把这两种情况视为没有必要执行finalize。当对象被判定有必要执行finalize时,对象将会被放置在一个叫做F-Queue的队列中,并在稍后的一个由虚拟机自动建立的、优先级低的一个Finalizer线程去出发这些对象的finalize(要注意的是,虚拟机并不承诺会等待这些对象finalize方法执行结束,这是因为如果一个对象的finalize方法执行缓慢、或者发生死循环,将导致F-Queue队列其他对象处于永久等待,甚至导致内存回收系统崩溃)。finalize是对象逃脱回收的最后一次机会,GC会将F-Queue中的对象进行第二次小规模的标记,如果对象在finalize中重新和引用链连上了,那么就被移出回收集合,没有逃脱则将会被回收(要记住哦,对象的finalize只能被执行一次,也就是说当对象通过finalize逃脱回收之后,下一次如果再被可达性分析标记,那么就逃不了了)。

  •最后

  其实很多时候我们谈论回收都在java堆上进行的,上面对象实例都是在java堆上进行的,很少谈及方法区的回收,因为方法区(一般被称为永久代)中的回收条件很苛刻,比如在java堆上进行回收可以达到70%-95%的空间,在方法区却低很低,但并不代表方法区不能有垃圾回收,Java虚拟机规范中,只是说可以不要求在方法区实现回收机制。

原文地址:https://www.cnblogs.com/gnz49/p/12059462.html

时间: 2024-08-02 12:26:59

JVM如何判断对象能否被回收的相关文章

jvm如何判断对象是否可以被回收

内容基本来自周志明 深入理解Java虚拟机 第二版 第三章 .这本书还可以,不过好像也没什么其他中文的关于jvm比较好的书了 jvm要做垃圾回收时,首先要判断一个对象是否还有可能被使用.那么如何判断一个对象是否还有可能被用到? 如果我们的程序无法再引用到该对象,那么这个对象就肯定可以被回收,这个状态称为不可达.当对象不可达,该对象就可以作为回收对象被垃圾回收器回收. 那么这个可达还是不可达如何判断呢? 答案就是GC roots ,也就是根对象,如果从一个对象没有到达根对象的路径,或者说从根对象开

JVM的判断对象是否已死和四种垃圾回收算法总结

面试题一:判断对象是否已死 判断对象是否已死就是找出哪些对象是已经死掉的,以后不会再用到的,就像地上有废纸.饮料瓶和百元大钞,扫地前要先判断出地上废纸和饮料瓶是垃圾,百元大钞不是垃圾.判断对象是否已死有引用计数算法和可达性分析算法. 1.引用计数算法 给每一个对象添加一个引用计数器,每当有一个地方引用它时,计数器值加 1:每当有一个地方不再引用它时,计数器值减 1,这样只要计数器的值不为 0,就说明还有地方引用它,它就不是无用的对象.如下图,对象 2 有 1 个引用,它的引用计数器值为 1,对象

javaVM 判断对象实例何时回收 用的可达性分析算法,而非引用计数算法

做java开发也好几年了,今天才晓得java内存的回收算法,真是惭愧惭愧 java虚拟机判断一个对象实例是否可以被回收,并非引用计数算法. 因为引用计数算法很难解决对象直接互相循环引用的问题. 所以java C#都是使用可达性分析来判断对象是否可以回收的. 这个算法的基本思路就是通过一系列的称为"GC Root"的对象作为起始点,从这些节点开始向下搜素,搜索所走过的路径称为应用链,当一个对象到GC Roots没有任何引用链相连时.则证明此对象时不可用的,可以被回收了.如下图对象obje

JVM虚拟机-03、JVM内存分配机制与垃圾回收算法

JVM虚拟机-03.JVM内存分配机制与垃圾回收算法 1 JVM内存分配与回收 1.1 对象优先在Eden区分配 大多数情况下,对象在新生代中?Eden?区分配.当?Eden?区没有足够空间进行分配时,虚拟机将发起一次Minor?GC.我们来进行实际测试一下.在测试之前我们先来看看?Minor?GC和Full?GC?有什么不同呢? Minor?GC/Young?GC:指发生新生代的的垃圾收集动作,MinorGC非常频繁,回收速度一般也比较快. Major?GC/Full?GC:一般会回收老年代,

JVM【第十回】:【判断对象已死之引用计数算法】

很多教科书判断对象是否存活的算法是这样的:给对象添加一个引用计数器,每当有一个地方引用它时,计数器值就加1:当引用失效时,计数器值就减1:任何时刻计数器都为0的对象就是不可能再被使用.很多应届生和一些有多年工作经验的开发人员,他们对于这个问题给予的都是这个答案. 客观地说,引用计数算法的实现建安,判定效率也很高,在大部分情况下它都是一个不错的算法,也有一些比较著名的应用案例,例如微软COM(Component Object Model)技术.使用ActionScript3的FlashPlayer

JVM【第十一回】:【判断对象已死之根搜索算法】

在主流的商用程序语言中(Java和C#)都是使用根搜索算法(GC Roots Tracing)判断对象是否存活的.这个算法的基本思路:通过一系列的名为"GC Roots"的对象作为起始点,从这些节点开始向下搜索,搜索所走过的路径称为引用链(Reference Chain),当一个对象到GC Roots没有任何引用链相连(就是从GC Roots到这个对象不可达)时,则证明对象是不可用的.如下图所示,对象object5.object6.object7虽然互相有关联,但是它们到GC Root

JVM理论:(二/2)判断对象是否已死

讲到垃圾回收,首先就要先知道哪些对象是可以回收的. 可达性算法 这里有必要先了解一下可达性算法,以"GC Roots"的对象作为起始点,若从"GC Roots"到某对象不可达时,此对象会被判定为可回收对象. 可作为GC Roots的对象包括下面几种: 1.虚拟机栈(栈帧中的本地变量表)中引用的对象. 2.方法区中类静态属性引用的对象. 3.方法区中常量引用的对象. ?有疑问 4.本地方法栈中JNI(native方法)引用的对象. * 作为扩展知识了解一下引用计数法,

必知必会JVM垃圾回收——对象搜索算法与回收算法

垃圾回收(GC)是JVM的一大杀器,它使程序员可以更高效地专注于程序的开发设计,而不用过多地考虑对象的创建销毁等操作.但是这并不是说程序员不需要了解GC.GC只是Java编程中一项自动化工具,任何一个工具都有它适用的范围,当超出它的范围的时候,可能它将不是那么自动,而是需要人工去了解与适应地适用. 拥有一定工作年限的程序员,在工作期间肯定会经常碰到像内存溢出.内存泄露.高并发的场景.这时候在应对这些问题或场景时,如果对GC不了解,很可能会成为个人的发展瓶颈. 接下来的两文将详细学习下JVM中垃圾

JVM学习-jvm判断对象已死的方法

在堆里面存放着各种各类的Java对象,垃圾收集器在对堆进行垃圾回收时,首要就是判断哪些对象还活着,哪些对象已经死去(即不被任何途径引用的对象). 标记清除算法: 标记清除算法简单概括为:给对象添加一个引用计数器,每当有一个地方引用该对象时,计数器+1,当引用失效时,计数器-1,任何时刻,当计数器为0的时候,该对象不再被引用.客观的说,引用计数器的实现简单,判定效率也高,大部分场景下是一个不错的选择.但是,当前主流的Jvm均没有采用标记清除算法,原因在于,它很难解决对象之间互相循环调用的情况. 可