关于强引用、软引用、弱引用、幻象引用,你该如何回答?

软件发布|下载排行|最新软件

当前位置:首页IT学院IT技术

关于强引用、软引用、弱引用、幻象引用,你该如何回答?

程序员cxuan   2020-04-25 我要评论
我们说的不同的引用类型其实都是逻辑上的,而对于虚拟机来说,主要体现的是对象的不同的`可达性(reachable)` 状态和对`垃圾收集(garbage collector)`的影响。 ## 初识引用 对于刚接触 Java 的 C++ 程序员而言,理解栈和堆的关系可能很不习惯。在 C++ 中,可以使用 new 操作符在堆上创建对象,或者使用自动分配在栈上创建对象。下面的 C++ 语句是合法的,但是 Java 编译器却拒绝这么写代码,会出现 `syntax error` 编译错误。 ```java Integer foo = Integer(1); ``` Java 和 C 不一样,Java 中会把对象都放在堆上,需要 new 操作符来创建对象。本地变量存储在`栈`中,它们持有一个指向堆中对象的`引用(指针)`。下面是一个 Java 方法,该方法具有一个 Integer 变量,该变量从 String 解析值 ```java public static void foo(String bar){ Integer baz = new Integer(bar); } ``` 这段代码我们使用堆栈分配图可以看一下它们的关系 ![](https://img2020.cnblogs.com/blog/1515111/202004/1515111-20200425181959328-304051317.png) 首先先来看一下 `foo()` 方法,这一行代码分配了一个新的 Integer 对象,JVM 尝试在堆空间中开辟一块内存空间。如果允许分配的话,就会调用 Integer 的构造方法把 String 字符串转换为 Integer 对象。JVM 将指向该对象的指针存储在变量 baz 中。 上面这种情况是我们乐意看到的情况,毕竟我们不想在编写代码的时候遇到阻碍,但是这种情况是不可能出现的,当堆空间无法为 bar 和 baz 开辟内存空间时,就会出现 `OutOfMemoryError`,然后就会调用`垃圾收集器(garbage collector)` 来尝试腾出内存空间。这中间涉及到一个问题,垃圾收集器会回收哪些对象? ## 垃圾收集器 Java 给你提供了一个 new 操作符来为堆中的对象开辟内存空间,但它没有提供 `delete` 操作符来释放对象空间。当 foo() 方法返回时,如果变量 baz 超过最大内存,但它所指向的对象仍然还在堆中。如果没有垃圾回收器的话,那么程序就会抛出 `OutOfMemoryError` 错误。然而 Java 不会,它会提供垃圾收集器来释放不再引用的对象。 当程序尝试创建新对象并且堆中没有足够的空间时,垃圾收集器就开始工作。当收集器访问堆时,请求线程被挂起,试图查找程序不再主动使用的对象,并回收它们的空间。如果垃圾收集器无法释放足够的内存空间,并且JVM 无法扩展堆,则会出现 `OutOfMemoryError`,你的应用程序通常在这之后崩溃。还有一种情况是 `StackOverflowError` ,它出现的原因是因为线程请求的栈深度要大于虚拟机所允许的深度时出现的错误。 ###标记 - 清除算法 Java 能永久不衰的一个原因就是因为垃圾收集器。许多人认为 JVM 会为每个对象保留一个引用计数,当每次引用对象的时候,引用计数器的值就 + 1,当引用失效的时候,引用计数器的值就 - 1。而垃圾收集器只会回收引用计数器的值为 0 的情况。这其实是 `引用计数法(Reference Counting)` 的收集方式。但是这种方式无法解决对象之间相会引用的问题,如下 ```java class A{ public B b; } class B{ public A a; } public class Main{ public static void main(String[] args){ A a = new A(); B b = new B(); a.b=b; b.a=a; } } ``` 然而实际上,JVM 使用一种叫做 `标记-清除(Mark-Sweep)`的算法,标记清除垃圾回收背后的想法很简单:程序无法到达的每个对象都是垃圾,可以进行回收。 标记-清除收集具有如下几个阶段 * 阶段一:标记 垃圾收集器会从 `根(root)` 引用开始,标记它到达的所有对象。如果用老师给学生判断卷子来比喻,这就相当于是给试卷上的全部答案判断正确还是错误的过程。 ![](https://img2020.cnblogs.com/blog/1515111/202004/1515111-20200425182008904-1654343562.png) * 阶段二:清理 在第一阶段中所有可回收的的内容都能够被垃圾收集器进行回收。如果一个对象被判定为是可以回收的对象,那么这个对象就被放在一个 `finalization queue(回收队列)`中,并在稍后会由一个虚拟机自动建立的、低优先级的 `finalizer` 线程去执行它。 ![](https://img2020.cnblogs.com/blog/1515111/202004/1515111-20200425182015131-1670094241.png) * 阶段三:整理(可选) 一些收集器有第三个步骤,整理。在这个步骤中,GC 将对象移动到垃圾收集器回收完对象后所留下的自由空间中。这么做可以防止堆碎片化,防止大对象在堆中由于堆空间的不连续性而无法分配的情况。 ![](https://img2020.cnblogs.com/blog/1515111/202004/1515111-20200425182022415-1609544787.png) 所以上面的过程中就涉及到一个`根节点(GC Roots)` 来判断是否存在需要回收的对象。这个算法的基本思想就是通过一系列的 `GC Roots` 作为起始点,从这些节点向下搜索,搜索所走过的路径称为 `引用链(Reference Chain)`,当一个对象到 GC Roots 之间没有任何引用链相连的话,则证明此对象不可用。引用链上的任何一个能够被访问的对象都是`强引用` 对象,垃圾收集器不会回收强引用对象。 因此,返回到 foo() 方法中,仅在执行方法时,参数 bar 和局部变量 baz 才是强引用。一旦方法执行完成,它们都超过了作用域的时候,它们引用的对象都会进行垃圾回收。 下面来考虑一个例子 ```java LinkedList foo = new LinkedList(); foo.add(new Integer(111)); ``` 变量 foo 是一个强引用,它指向一个 LinkedList 对象。LinkedList(JDK.18) 是一个链表的数据结构,每一个元素都会指向前驱元素,每个元素都有其后继元素。 ![](https://img2020.cnblogs.com/blog/1515111/202004/1515111-20200425182029906-467699465.png) 当我们调用`add()` 方法时,我们会增加一个新的链表元素,并且该链表元素指向值为 111 的 Integer 实例。这是一连串的强引用,也就是说,这个 Integer 的实例不符合垃圾收集条件。一旦 foo 对象超出了程序运行的作用域,LinkedList 和其中的引用内容都可以进行收集,收集的前提是没有强引用关系。 ### Finalizers C++ 允许对象定义析构函数方法:当对象超出作用范围或被明确删除时,会调用析构函数来清理使用的资源。对于大多数对象来说,析构函数能够释放使用 new 或者 malloc 函数分配的内存。 在Java中,垃圾收集器会为你自动清除对象,分配内存,因此不需要显式析构函数即可执行此操作。这也是 Java 和 C++ 的一大区别。 然而,内存并不是唯一需要被释放的资源。考虑 `FileOutputStream`:当你创建此对象的实例时,它从操作系统分配文件句柄。如果你让流的引用在关闭前超过了其作用范围,该文件句柄会怎么样?实际上,每个流都会有一个 `finalizer` 方法,这个方法是垃圾回收器在回收之前由 JVM 调用的方法。对于 FileOutputStream 来说,**finalizer 方法会关闭流,释放文件句柄给操作系统,然后清除缓冲区,确保数据能够写入磁盘**。 任何对象都具有 finalizer 方法,你要做的就是声明 `finalize()` 方法。如下 ```java protected void finalize() throws Throwable { // 清除对象 } ``` 虽然 finalizers 的 finalize() 方法是一种好的清除方式,但是这种方法产生的负面影响非常大,你不应该依靠这个方法来做任何垃圾回收工作。因为 `finalize` 方法的运行开销比较大,不确定性强,无法保证各个对象的调用顺序。finalize 能做的任何事情,可以使用 `try-finally` 或者其他方式来做,甚至做的更好。 ## 对象的生命周期 综上所述,可以通过下面的流程来对对象的生命周期做一个总结 ![](https://img2020.cnblogs.com/blog/1515111/202004/1515111-20200425182038141-1201935650.png) 对象被创建并初始化,对象在运行时被使用,然后离开对象的作用域,对象会变成不可达并会被垃圾收集器回收。图中用红色标明的区域表示对象处于强可达阶段。 JDK1.2 介绍了 `java.lang.ref` 包,对象的生命周期有四个阶段:`

Copyright 2022 版权所有 软件发布 访问手机版

声明:所有软件和文章来自软件开发商或者作者 如有异议 请与本站联系 联系我们