-
2021-02-12 17:05:48
垃圾回收
垃圾回收,就是将已经分配出去的,但却不再使用的内存收回来,以便能搞再次分配。
在Java虚拟机的语境下,垃圾指的是死亡的对象所占据的堆空间。
如何辨别一个对象是存是亡?
一种古老的辨别方法: 可以通过引用计数法(reference counting),为每个对象添加一个引用计数器,用来统计指向该对象的引用个数。 一旦某个对象的引用计数器为0,则说明该对象已经死亡,便可以被回收了。
目前Java虚拟机的主流垃圾回收器采取的是可达性分析算法。
这个算法的实质在于将一系列GC Roots作为初始的存活对象合集(live set),然后从该合集出发,探索所有能够被该合集引用到的对象,并将其加入到该和集中,这个过程称之为标记(mark)。 最终,未被探索到的对象便是死亡的,是可以回收的。
引用计数法的实现
如果有一个引用,被赋值为某一对象,那么将该对象的引用计数器+1。 如果一个指向某一对象的引用,被赋值为其他值,那么将该对象的引用计数器-1。 需要截获所有的引用更新操作,并且相应地增减目标对象的引用计数器。
引用计数法的缺点
一是需要额外的空间来存储计数器,以及繁琐的更新操作。
二是无法处理循环引用对象。
假设对象a与b相互引用,除此之外没有其他引用指向a或者b。 在这种情况下,a和b实际上已经死了,但由于它们的引用计数器皆不为0,在引用计数器的心中,这两个对象还活着。 因此,这些循环引用对象所占据的空间将不可回收,从而造成了内存泄漏。
GC Roots
GC Roots可以理解为由堆外指向堆内的引用, 一般而言,GC Roots包括(但不限于)以下几种:Java 方法栈桢中的局部变量;
已加载类的静态变量;
JNI handles;
已启动且未停止的 Java 线程。
可达性分析算法的优点
可达性分析可以解决引用计数器所不能解决的循环引用问题。
即便对象a和b相互引用,只要从GC Roots出发无法到达a或者b,那么可达性分析便不会将它们加入存活对象合集之中。
可达性分析算法的不足
在多线程环境下,其他线程可能会更新已经访问过的对象中的引用,从而造成误报(将引用设置为null)或者漏报(将引用设置为未被访问过的对象)。
误报并没有什么伤害,Java虚拟机至多损失了部分垃圾回收的机会。
漏报则比较麻烦,因为垃圾回收器可能回收事实上仍被引用的对象内存。 一旦从原引用访问已经被回收了的对象,则很有可能会直接导致Java虚拟机崩溃。
更多相关内容 -
可达性分析算法
2021-07-21 18:54:27可达性分析算法:也可以称为根搜索算法、追踪性垃圾收集。 相对于引用计数算法而言,可达性分析算法不仅同样具备实现简单和执行高效等特点,更重要的是该算法可以有效地解决在引用计数算法中循环引用的问题,防止...一 概念
可达性分析算法:也可以称为根搜索算法、追踪性垃圾收集。
相对于引用计数算法而言,可达性分析算法不仅同样具备实现简单和执行高效等特点,更重要的是该算法可以有效地解决在引用计数算法中循环引用的问题,防止内存泄漏的发生。
相较于引用计数算法,这里的可达性分析就是 Java、C# 选择的。这种类型的垃圾收集通常也叫作追踪性垃圾收集(Tracing Garbage Collection)。
二 思路
所谓 "GC Roots”根集合就是一组必须活跃的引用。
基本思路:
-
可达性分析算法是以根对象集合(GCRoots)为起始点,按照从上至下的方式搜索被根对象集合所连接的目标对象是否可达。
-
使用可达性分析算法后,内存中的存活对象都会被根对象集合直接或间接连接着,搜索所走过的路径称为引用链(Reference Chain)。
-
如果目标对象没有任何引用链相连,则是不可达的,就意味着该对象己经死亡,可以标记为垃圾对象。
-
在可达性分析算法中,只有能够被根对象集合直接或者间接连接的对象才是存活对象。
三 GC Roots 可以是哪些?
-
虚拟机栈中引用的对象
比如:各个线程被调用的方法中使用到的参数、局部变量等。
-
本地方法栈内 JNI(通常说的本地方法)引用的对象
-
方法区中类静态属性引用的对象
比如:Java类的引用类型静态变量
-
方法区中常量引用的对象
比如:字符串常量池(string Table)里的引用
-
所有被同步锁 synchronized 持有的对象
-
Java虚拟机内部的引用。
基本数据类型对应的 Class 对象,一些常驻的异常对象(如:NullPointerException、OutOfMemoryError),系统类加载器。
-
反映 java 虚拟机内部情况的 JMXBean、JVMTI 中注册的回调、本地代码缓存等。
四 总结
总结一句话就是,堆空间外的一些结构,比如虚拟机栈、本地方法栈、方法区、字符串常量池等地方对堆空间进行引用的,都可以作为 GC Roots 进行可达性分析。
除了这些固定的 GC Roots 集合以外,根据用户所选用的垃圾收集器以及当前回收的内存区域不同,还可以有其他对象“临时性”地加入,共同构成完整 GC Roots 集合。比如:分代收集和局部回收(Partial GC)。
如果只针对 Java 堆中的某一块区域进行垃圾回收(比如:典型的只针对新生代),必须考虑到内存区域是虚拟机自己的实现细节,更不是孤立封闭的,这个区域的对象完全有可能被其他区域的对象所引用,这时候就需要一并将关联的区域对象也加入 GCRoots 集合中去考虑,才能保证可达性分析的准确性。
五 小技巧
由于 Root 采用栈方式存放变量和指针,所以如果一个指针,它保存了堆内存里面的对象,但是自己又不存放在堆内存里面,那它就是一个 Root。
六 注意
如果要使用可达性分析算法来判断内存是否可回收,那么分析工作必须在一个能保障一致性的快照中进行。这点不满足的话分析结果的准确性就无法保证。
这点也是导致 GC进行时必须“stop The World”的一个重要原因。
即使是号称(几乎)不会发生停顿的 CMS 收集器中,枚举根节点时也是必须要停顿的。
-
-
可达性分析算法代码举例
2022-01-18 20:08:37即使在可达性分析算法中判定为不可达的对象,也不是“非死不可”的,这时候它们暂时还处于“缓 刑”阶段,要真正宣告一个对象死亡,至少要经历两次标记过程:如果对象在进行可达性分析后发现没 有与GC Roots相连接的...看深入理解java虚拟机的时候看到3.2.4节生存还是死亡(to be or not to be)的时候看到的一段代码
有趣分享给大家
即使在可达性分析算法中判定为不可达的对象,也不是“非死不可”的,这时候它们暂时还处于“缓 刑”阶段,要真正宣告一个对象死亡,至少要经历两次标记过程:如果对象在进行可达性分析后发现没 有与GC Roots相连接的引用链,那它将会被第一次标记,随后进行一次筛选,筛选的条件是此对象是 否有必要执行finalize()方法。假如对象没有覆盖finalize()方法,或者finalize()方法已经被虚拟机调用 过,那么虚拟机将这两种情况都视为“没有必要执行”。
如果这个对象被判定为确有必要执行finalize()方法,那么该对象将会被放置在一个名为F-Queue的 队列之中,并在稍后由一条由虚拟机自动建立的、低调度优先级的Finalizer线程去执行它们的finalize() 方法。这里所说的“执行”是指虚拟机会触发这个方法开始运行,但并不承诺一定会等待它运行结束。 这样做的原因是,如果某个对象的finalize()方法执行缓慢,或者更极端地发生了死循环,将很可能导 致F-Queue队列中的其他对象永久处于等待,甚至导致整个内存回收子系统的崩溃。finalize()方法是对 象逃脱死亡命运的最后一次机会,稍后收集器将对F-Queue中的对象进行第二次小规模的标记,如果对 象要在finalize()中成功拯救自己——只要重新与引用链上的任何一个对象建立关联即可,譬如把自己 (this关键字)赋值给某个类变量或者对象的成员变量,那在第二次标记时它将被移出“即将回收”的集 合;如果对象这时候还没有逃脱,那基本上它就真的要被回收了。从代码清单3-2中我们可以看到一个 对象的finalize()被执行,但是它仍然可以存活。
public class FinalizeEscapeGc { public static FinalizeEscapeGc SAVE_HOOK = null; public void isAlive(){ System.out.println("yes, i am still alive :)"); } @Override protected void finalize() throws Throwable { super.finalize(); System.out.println("finalize method executed!"); FinalizeEscapeGc.SAVE_HOOK = this; } public static void main(String[] args) throws InterruptedException { SAVE_HOOK = new FinalizeEscapeGc(); //对象第一次成功拯救自己 SAVE_HOOK = null; System.gc(); /// 因为Finalizer方法优先级很低,暂停0.5秒,以等待它 Thread.sleep(500); if (SAVE_HOOK != null) {//走进这里说明gc出发finalize了 SAVE_HOOK.isAlive(); } else { System.out.println("no, i am dead :("); } // 下面这段代码与上面的完全相同,但是这次自救却失败了 SAVE_HOOK = null; System.gc(); // 因为Finalizer方法优先级很低,暂停0.5秒,以等待它 Thread.sleep(500); if (SAVE_HOOK != null) { SAVE_HOOK.isAlive(); } else {//finalize只允许执行依次 System.out.println("no, i am dead :("); } } }
运行结果:
我们在main方法中设置了SAVE_HOOK = null;相当于这个对象为null,当我们调用System.gc();垃圾回收的时候,虽然这个对象被认为是不可达的,但是因为覆盖了finalize()方法,所以会执行finalize()方法,当我们第二次调用System.gc();垃圾回收因为finalize()方法已经被执行了一遍了不能再执行所以此时真正认为该对象为垃圾对象。这就是上述的可达性分析的两次标记
-
JVM:可达性分析算法
2019-09-03 23:21:11一、可达性分析算法 在Java中,是通过可达性分析(Reachability Analysis)来判定对象是否存活的。该算法的基本思路就是通过一些被称为引用链(GC Roots)的对象作为起点,从这些节点开始向下搜索,搜索走过的路径...在堆里存放着几乎多有的java对象实例,垃圾搜集器在对堆进行回收之前,第一件事情就是确定这些对象之中哪些还“存活”着(即通过任何途径都无法使用的对象)。
一、可达性分析算法
在Java中,是通过可达性分析(Reachability Analysis)来判定对象是否存活的。该算法的基本思路就是通过一些被称为引用链(GC Roots)的对象作为起点,从这些节点开始向下搜索,搜索走过的路径被称为(Reference Chain),当一个对象到GC Roots没有任何引用链相连时(即从GC Roots节点到该节点不可达),则证明该对象是不可用的。
可达性分析算法判断对象是否可以回收
如上图所示,object1~object4对GC Root都是可达的,说明不可被回收,object5和object6对GC Root节点不可达,说明其可以被回收。
在Java中,可作为GC Root的对象包括以下几种:- 虚拟机栈(栈帧中的本地变量表)中引用的对象
- 方法区中类静态属性引用的对象
- 方法区中常量引用的对象
- 本地方法栈中JNI(即一般说的Native方法)引用的对象
二、finalize()方法最终判定对象是否存活
即使在可达性分析算法中不可达的对象,也并非是“非死不可”的,这时候它们暂时处于“缓刑”阶段,要真正宣告一个对象死亡,至少要经历再次标记过程。
标记的前提是对象在进行可达性分析后发现没有与GC Roots相连接的引用链。
1. 第一次标记并进行一次筛选。
筛选的条件是此对象是否有必要执行finalize()方法。
当对象没有覆盖finalize方法,或者finzlize方法已经被虚拟机调用过,虚拟机将这两种情况都视为“没有必要执行”,对象被回收。
2. 第二次标记
如果这个对象被判定为有必要执行finalize()方法,那么这个对象将会被放置在一个名为:F-Queue的队列之中,并在稍后由一条虚拟机自动建立的、低优先级的Finalizer线程去执行。这里所谓的“执行”是指虚拟机会触发这个方法,但并不承诺会等待它运行结束。这样做的原因是,如果一个对象finalize()方法中执行缓慢,或者发生死循环(更极端的情况),将很可能会导致F-Queue队列中的其他对象永久处于等待状态,甚至导致整个内存回收系统崩溃。
Finalize()方法是对象脱逃死亡命运的最后一次机会,稍后GC将对F-Queue中的对象进行第二次小规模标记,如果对象要在finalize()中成功拯救自己----只要重新与引用链上的任何的一个对象建立关联即可,譬如把自己赋值给某个类变量或对象的成员变量,那在第二次标记时它将移除出“即将回收”的集合。如果对象这时候还没逃脱,那基本上它就真的被回收了。三、Java引用
从可达性算法中可以看出,判断对象是否可达时,与“引用”有关。那么什么情况下可以说一个对象被引用,引用到底代表什么?
在JDK1.2之后,Java对引用的概念进行了扩充,可以将引用分为以下四类:- 强引用(Strong Reference)
- 软引用(Soft Reference)
- 弱引用(Weak Reference)
- 虚引用(Phantom Reference)
这四种引用从上到下,依次减弱
3.1 强引用
强引用就是指在程序代码中普遍存在的,类似
Object obj = new Object()
这类似的引用,只要强引用在,垃圾搜集器永远不会搜集被引用的对象。也就是说,宁愿出现内存溢出,也不会回收这些对象。3.2 软引用
软引用是用来描述一些有用但并不是必需的对象,在Java中用
java.lang.ref.SoftReference
类来表示。对于软引用关联着的对象,只有在内存不足的时候JVM才会回收该对象。因此,这一点可以很好地用来解决OOM的问题,并且这个特性很适合用来实现缓存:比如网页缓存、图片缓存等。import java.lang.ref.SoftReference; public class Main { public static void main(String[] args) { SoftReference<String> sr = new SoftReference<String>(new String("hello")); System.out.println(sr.get()); } }
3.3 弱引用
弱引用也是用来描述非必需对象的,当JVM进行垃圾回收时,无论内存是否充足,都会回收被弱引用关联的对象。在java中,用java.lang.ref.WeakReference类来表示。下面是使用示例:
import java.lang.ref.WeakReference; public class Main { public static void main(String[] args) { WeakReference<String> sr = new WeakReference<String>(new String("hello")); System.out.println(sr.get()); System.gc(); //通知JVM的gc进行垃圾回收 System.out.println(sr.get()); } }
3.4 虚引用
虚引用和前面的软引用、弱引用不同,它并不影响对象的生命周期。在java中用
java.lang.ref.PhantomReference
类表示。如果一个对象与虚引用关联,则跟没有引用与之关联一样,在任何时候都可能被垃圾回收器回收。
要注意的是,虚引用必须和引用队列关联使用,当垃圾回收器准备回收一个对象时,如果发现它还有虚引用,就会把这个虚引用加入到与之 关联的引用队列中。程序可以通过判断引用队列中是否已经加入了虚引用,来了解被引用的对象是否将要被垃圾回收。如果程序发现某个虚引用已经被加入到引用队列,那么就可以在所引用的对象的内存被回收之前采取必要的行动。import java.lang.ref.PhantomReference; import java.lang.ref.ReferenceQueue; public class Main { public static void main(String[] args) { ReferenceQueue<String> queue = new ReferenceQueue<String>(); PhantomReference<String> pr = new PhantomReference<String>(new String("hello"), queue); System.out.println(pr.get()); } }
3.5 软引用和弱引用进一步说明
在SoftReference类中,有三个方法,两个构造方法和一个get方法(WekReference类似):
public class SoftReference<T> extends Reference<T> { /** * Timestamp clock, updated by the garbage collector */ static private long clock; /** * Timestamp updated by each invocation of the get method. The VM may use * this field when selecting soft references to be cleared, but it is not * required to do so. */ private long timestamp; /** * Creates a new soft reference that refers to the given object. The new * reference is not registered with any queue. * * @param referent object the new soft reference will refer to */ public SoftReference(T referent) { super(referent); this.timestamp = clock; } /** * Creates a new soft reference that refers to the given object and is * registered with the given queue. * * @param referent object the new soft reference will refer to * @param q the queue with which the reference is to be registered, * or <tt>null</tt> if registration is not required * */ public SoftReference(T referent, ReferenceQueue<? super T> q) { super(referent, q); this.timestamp = clock; } /** * Returns this reference object's referent. If this reference object has * been cleared, either by the program or by the garbage collector, then * this method returns <code>null</code>. * * @return The object to which this reference refers, or * <code>null</code> if this reference object has been cleared */ public T get() { T o = super.get(); if (o != null && this.timestamp != clock) this.timestamp = clock; return o; } }
get方法用来获取与软引用关联的对象的引用,如果该对象被回收了,则返回null。
在使用软引用和弱引用的时候,我们可以显示地通过System.gc()来通知JVM进行垃圾回收,但是要注意的是,虽然发出了通知,JVM不一定会立刻执行,也就是说这句是无法确保此时JVM一定会进行垃圾回收的。
3.6 虚引用进一步说明:
虚引用中有一个构造函数,可以看出,其必须和一个引用队列一起存在。get()方法永远返回null,因为虚引用永远不可达。
public class PhantomReference<T> extends Reference<T> { /** * Returns this reference object's referent. Because the referent of a * phantom reference is always inaccessible, this method always returns * <code>null</code>. * * @return <code>null</code> */ public T get() { return null; } /** * Creates a new phantom reference that refers to the given object and * is registered with the given queue. * * <p> It is possible to create a phantom reference with a <tt>null</tt> * queue, but such a reference is completely useless: Its <tt>get</tt> * method will always return null and, since it does not have a queue, it * will never be enqueued. * * @param referent the object the new phantom reference will refer to * @param q the queue with which the reference is to be registered, * or <tt>null</tt> if registration is not required */ public PhantomReference(T referent, ReferenceQueue<? super T> q) { super(referent, q); } }
参考
- 《深入理解Java虚拟机》
-
jvm之可达性分析算法
2020-11-21 22:20:54可达性分析算法 目前主流的商用JVM都是通过可达性分析来判断对象是否可以被回收的。 这个算法的基本思路是: 通过一系列被称为「GC Roots」的根对象作为起始节点集,从这些节点开始,通过引用关系向下搜寻,搜寻... -
大白话理解可达性分析算法
2020-10-24 21:39:29可达性分析算法 引用计数算法 在对象中添加一个引用计数器,每当新加一个引用时,计数器就加1,当引用失效时,计数器就减1。任何时刻只要计数器为0就代表对象没有引用可以被回收。 这种算法实现简单,判断高效,... -
可达性分析算法GC Roots
2021-09-09 14:24:27当前主流的商用程序语言(Java、C#,上溯至前面提到的古老的Lisp)的内存管理子系统,都是通过可达性分析(Reachability Analysis)算法来判定对象是否存活的。这个算法的基本思路就是通过一系列称为“GC Roots”的... -
JVM HotSpot 可达性分析算法实现细节
2020-12-26 16:51:05在之前关于可达性分析算法的介绍中我们讲过,我们需要先找出可固定作为 GC Roots 的节点,然后沿着引用链去寻找那些无用的垃圾对象。GC Roots 节点一般在全局性引用(例如常量和类静态属性)与执行上下文(例如栈帧... -
垃圾标记阶段算法之可达性分析算法
2021-04-22 14:15:43可达性分析(或根搜索算法、追踪性垃圾收集)可达性分析概述相对于引用计数算法而言,可达性分析算法不仅同样具备实现简单和执行高效率的特点,更重要的是**该算法可以有效地解决在引用计数算法中循环引用的问题,防止... -
java-可达性分析算法和引用
2021-11-03 10:56:27Java并不采用引用计数法来判断对象是否已“死”,而采用“可达性分析”来判断对象是否存活(同样采用此法的还有C#、Lisp-最早的一门采用动态内存分配的语言)。 此算法的核心思想:通过一系列称为“GC Roots”的... -
JVM垃圾标记算法之可达性分析算法
2022-03-10 19:55:21可达性分析算法也叫根搜索算法,追踪性垃圾收集 二、相较于引用计数算法: 三、可达性分析算法实现思路: 所谓的GC Roots根集合就是一组必须活跃的引用。 图解: 四、Java中 GC Roots 包括哪些元素: ... -
引用计数法和可达性分析算法
2021-11-23 20:42:27垃圾回收的区域:方法区,堆 引用计数法 栈中有对堆中的对象有引用,如果失去了这个引用关系,那么这个对象就需要被回收 在引用计数法中当一个对象被...可达性分析算法:从GC root 出发,向下开始搜索引用的对象,搜索经 -
三色标记(可达性分析算法)及一些思考
2022-02-08 17:59:15Java对象存活的判断、可达性分析算法及三色标记 -
垃圾回收之标记阶段-可达性分析算法
2022-04-30 16:52:16可达性分析算法 可达性分析算法:也可以称为 根搜索算法、追踪性垃圾收集 相对于引用计数算法而言,可达性分析算法不仅同样具备实现简单和执行高效等特点,更重要的是该算法可以有效地解决在引用计数算法中循环引用... -
关于可达性分析算法的概述
2020-09-25 22:01:01可达性分析算法主要是用来判断对象是否存活的。这个算法的基本思路是通过一系列称为”GC Roots“的对象作为起始点,从这些节点开始往下搜索,搜索所走过的路径称为引用链,当一个对象到GC Roots没有任何引用链相连... -
引用计数算法和可达性分析算法
2020-04-06 15:28:38引用计数算法: 给对象中添加一个引用计数器,每当有一个地方引用它时,计数器值就加1;当引用失效时,计数器值就减1;任何时刻计数器为0的对象就是不可能再被使用的。 引用计数算法(ReferenceCounting)的实现... -
可达性分析算法中根节点有哪些
2021-08-31 21:19:001.虚拟机栈中局部变量表的对象 2.方法区中的静态属性 3.方法区中的静态对象 4.native方法引用的对象 -
可达性分析算法(GC ROOT)的过程
2020-11-12 14:24:30可达性分析算法: (当前主流语言的内存管理子系统都是通过可达性分析算法来判断对象是否存活,Java语言同样,没有使用引用计数法算法的) 基本思路:GC roots的对象作为起始节点集合,从这些节点开始,根据引用... -
GC:可达性分析算法
2020-02-20 21:27:55可达性分析算法之前有一个叫引用计数法,原理非常简单如果一个对象没有任何引用与之关联,基本上对象不可能在其他地方用的上,这个对象就成为可被回收的对象,但是这个方法虽然非常简单,而且高效,但是它无法解决... -
JVM是如何判定对象为垃圾的(可达性分析算法)
2021-02-20 16:37:55下面就要说起经常作为垃圾判定依据的可达性分析算法与引用计数法了。这两种算法,都是经常被用作垃圾判定的算法,下面说下这两种算法。 引用计数法 为对象添加一个引用计数器,当有一个对象引用了该对象时,... -
6、垃圾回收概述、垃圾回收算法(可达性分析算法、标记清除算法、复制算法、标记压缩算法)
2020-09-17 00:45:26文章目录垃圾回收概述垃圾回收相关算法标记阶段:引用计数法标记阶段:可达性分析算法对象的finalization机制MAT的GC Roots溯源清除阶段:标记-清除算法清除阶段:复制算法清除阶段:标记-压缩算法小结分代收集算法... -
垃圾回收:可达性分析算法、生存还是死亡(两次标记)
2021-02-28 13:42:25判断哪些内存需要回收引用计数算法、可达性分析算法。引用计数算法优点实现简单,但是无法判断循环引用。可达性分析算法:通过一系列的GC ROOTS作为起点,往下搜索,所走过的路径叫引用链。当一个对象没有引用链到GC... -
JVM 垃圾回收算法 -可达性分析算法!!!高频面试!!!
2021-08-04 09:49:07前言:学习JVM,那么不可避免的要去了解JVM相关的垃圾回收算法,本文只是讲了讲了可达性分析算法,至于标记-清除、标记-复制,标记-整理,分代收集等等算法,会在近两天的文章中陆续更新出来。 很喜欢一句话:“八... -
JVM——引用计数算法与可达性分析算法
2018-12-12 23:56:14对于可达性分析算法中不可达的对象,它们也不会立刻就被回收,这个时候它们暂时处于“嫌疑人”状态,到真正宣告一个对象死亡,至少要经历两次标记过程。 如果对象在进行可达性分析之后被发现没有与GC Roots相连...