在过往的文章中主要讲述了JVM-Java虚拟机内存模型,接下来我们讲解一下Java对象在虚拟机中是如何判断存亡的,如何回收已经消亡的对象的呢。也就是Java对象存活算法
及垃圾回收算法
对象存活算法
在Java虚拟机中主要关注的也就是Java堆中的对象,因为这里面存放了Java世界中几乎所有的对象实例,要想对已经失去意义的对象也就是已经消亡的对象进行回收之前首先就要判断, 哪些对象还“存活”着,哪些对象已经“死去”(“死去”即不可 能再被任何途径使用的对象)了。 说到这里就不得不引出两个比较重要的算法:
引用计数算法
在很多教科书素材中都是这样提到的:会在对象中添加一个引用计数器,每当有一个地方引用它时,该计数器就会加一,当引用失效时,该计数器就会减一;任何计数器为零的时候即是意味着该对象不再被使用。引用计数算法其实效率是很高的,只不过是占用了一小部分空间,原理也比较简单。该算法也在很多知名的应用语言中有所应用比如:COM(Component Object Model)技术、使用 ActionScript 3 的 FlashPlayer、Python 语言以及在游戏脚本领域得到许多应用的 Squirrel 中都使用了引用 计数算法进行内存管理。
-
引用计数算法的优缺点 -
效率高,原理简单 -
无法解决对象之间相互循环引用的问题。这里可以引出spring是如何解决循环引用的问题?有兴趣的可以了解一下
话说回来,Java判断对象的存活就是根据引用计数算法吗?你觉得呢?实践出真知:通过配置**VM参数: **-XX:+PrintGCDetails
运行以下代码:
public class ReferenceCountingGC {
public Object instance = null;
private static final int _1MB = 1024 * 1024;
private byte[] bigSize = new byte[2 * _1MB];
/**
* 这个成员属性的唯一意义就是占点内存,以便能在 GC 日志中看清楚是否有回收过
*/
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();
// System.out.println("paidaxing +++++++++++++++++++++++");
// try {
// Thread.sleep(500000);
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
}
}
-
**结论 **
从运行结果中可以清楚看到内存回收日志中包含“840K->0K”,意味着虚拟机并 没有因为这两个对象互相引用就放弃回收它们,这也从侧面说明了 Java 虚拟机并不是通 过引用计数算法来判断对象是否存活的
。
关于gc日志观察Java内存分配与回收策略可参考文章:实战-通过gc日志观察Java内存分配与回收策略
可达性分析算法
当前主流的商用程序语言(Java、C#、以及古老的Lisp)的内存管理子系统都是通过可达性分析( Reachability Analysis )算法来判断对象是否存活的。其主要原理是通过一系列被称之为“GC Roots”的根对象作为起始节点集
从这些节点开始,根据引用关系向下搜索,搜索过程的路径称之为“引用链”
( Reachability Analysis );如果某个对象到达“GC Roots”没有任何的引用链相连,或者用图论的话来说就是从“GC Roots”到这个对象不可达时,则证明该对象就不会再被使用的。如图所示:对象 object 5、object 6、object 7 虽然互有关联,但是它们到 GC Roots 是不可达的,因此它们将会被判定为可回收的对象 。上面一直在说“GC Roots” 那么在Java技术体系中什么可以做为GC Roots呢?
-
在虚拟机栈(栈帧中的本地变量)中引用的对象,比如各个线程被调用的方法堆栈中使用到的参数、局部变量、临时变量等。 -
在方法区中类静态属性引用的对象,比如Java类的引用类型静态变量。 -
在方法区中常量引用的对象,比如字符串常量池(String Table)里的引用。 -
在本地方法栈中JNI(即通常所说的Native方法)引用的对象。 -
Java虚拟机内部的引用,如基本类型对应的Class对象,一些常驻的异常对象(比如NullPointException、OutOfMemoryError)等,还有系统类加载器。 -
所有被同步锁(synchronized)持有的对象。 -
反应Java虚拟机内部情况的JMXBean、JVMTI中注册的回调、本地代码缓存等。
以上就是主要的可以作为“GC Roots”的对象,当然除了这些固定的“GC Roots”集合外,还有其它对象临时性的加入 共同构成完整 GC Roots 集合。
引用
无论是通过引用计数算法判断对象的引用数量,还是通过可达性分析算法判断对象 是否引用链可达,判定对象是否存活都和“引用”离不开关系。
关于引用的概念在JDK1.2前后有所不同 JDK 1.2 之前
如果 reference 类型的数据中存储的数值代表的是另外一块 内存的起始地址,就称该 reference 数据是代表某块内存、某个对象的引用
JDK 1.2 之后 :
将引用划分了:
强引用
(Strongly Re-ference)、软引用
(Soft Reference)、弱引用
(Weak Reference)和虚引用
(Phantom Reference)4 种,这 4 种引用强度依次逐渐减弱。
强引用:
是最传统的引用定义,是指程序代码之间普遍存在的引用赋值 ,即类 似“Object obj=new Object()”这种引用关系。 所以无论任何情况下:只要强引用存在,垃圾收集器就永远不会回收被强引用的对象。
软引用:
是用来描述一些还有用,但是非必须的对象。只被软引用关联的对象,在系统发生内存溢出异常之前,会将这些对象列进回收范围之内进行二次回收,如果这次回收还是没有足够的内存,才会出现内存溢出的异常。JDK1.2版本之后使用SoftReference
类来实现软引用。
弱引用:
也是用来描述一些非必须的对象,但是它的强度还要毕软引用弱一些,被弱引用关联的对象只能存活到下一次垃圾收集发生为止,当垃圾收集器开始工作,无论是内存是否足够,都会回收掉被弱引用关联的对象。在JDK1.2版本之后使用WeakReference
类来实现软引用。
虚引用:
虚引用又被称为幽灵引用
、幻影引用
。它是一种最弱的引用关系。一个对象是否虚引用的存在,完全不对其生存时间构成影响,也无法通过一个虚引用取得一个对象的实例
。设置虚引用的目的就是:在这个对象被垃圾收集器回收时收到一个系统的通知
。JDK1.2版本之后,使用PhantomReference
类来实现虚引用。
讲完引用是否对一个对象的存活依据有了一些概念呢?那你觉得Java中判断对象中存活的依据仅是这些吗?当然不是,参考《深入理解Java虚拟机》中有这样一句话:
以上就是关于在Java中如何判断一个对象是否存活,以及通过什么方式判断存活,你了解了吗?为了避免篇幅过长在下一篇中讲解:JVM是如何回收已经消亡的对象的。
原文始发于微信公众号(码上遇见你):Java对象的存活是如何判断的呢?以及JVM如何回收已经消亡的对象
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/78582.html