page contents

精心总结:带你掌握JVM垃圾回收(上)

垃圾回收( Garbage Collection 以下简称 GC)诞生于1960年 MIT 的 Lisp 语言,有半个多世纪的历史。在Java 中,JVM 会对内存进行自动分配与回收,其中 GC 的主要作用就是清楚不再使用的对象,...

垃圾回收( Garbage Collection 以下简称 GC)诞生于1960年 MIT 的 Lisp 语言,有半个多世纪的历史。在Java 中,JVM 会对内存进行自动分配与回收,其中 GC 的主要作用就是清楚不再使用的对象,自动释放内存

GC 相关的研究者们主要是思考这3件事情。

  • 哪些内存需要回收?

  • 什么时候回收?

  • 如何回收?

本文也大致按照这个思路,为大家描述垃圾回收的相关知识。因为会有很多内存区域相关的知识,希望读者先学习完精美图文带你掌握 JVM 内存布局再来阅读本文。

在这里先感谢周志明大佬的新鲜出炉的大作:《深入理解Java 虚拟机》- 第3版

拜读之后对JVM有了更深的理解,强烈推荐大家去看。

本文的主要内容如下(建议大家在阅读和学习的时候,也大致按照以下的思路来思考和学习):

  • 哪些内存需要回收?即GC 发生的内存区域?

  • 如何判断这个对象需要回收?GC 的存活标准

    这里又能够引出以下的知识概念:

    • 引用计数法

    • 可达性分析法

    • 引用的种类和特点、区别 (强引用、软引用、弱引用、虚引用)

    • 延伸知识:(WeakHashMap) (引用队列)

  • 有了对象的存活标准之后,我们就需要知道GC 的相关算法(思想)

    • 标记-清除(Mark-Sweep)算法

    • 复制(Copying)算法

    • 标记-整理(Mark-Compact)算法

  • 在下一步学习之前,还需要知道一些GC的术语,防止对一些概念描述出现混淆

  • 知道了算法之后,自然而然我们到了JVM中对这些算法的实现和应用,即各种垃圾收集器(Garbage Collector)

    • 串行收集器

    • 并行收集器

    • CMS 收集器

    • G1 收集器

一、GC 的 目标区域

一句话:GC 主要关注 堆和方法区

精美图文带你掌握 JVM 内存布局一文中,理解介绍了Java 运行时内存的分布区域和特点。

其中我们知道了程序计数器、虚拟机栈、本地方法栈3个区域是随线程而生,随线程而灭的。栈中的栈帧随着方法的进入和退出而有条不紊地执行着出栈和入栈操作。每一个栈帧中分配多少内存基本上是在类结构确定下来时就已知的(尽管在运行期会由JIT编译器进行一些优化,但在本章基于概念模型的讨论中,大体上可以认为是编译期可知的),因此这几个区域的内存分配和回收都具备确定性,在这几个区域内就不需要过多考虑回收的问题,因为方法结束或者线程结束时,内存自然就跟随着回收了。

堆和方法区则不一样,一个接口中的多个实现类需要的内存可能不一样,一个方法中的多个分支需要的内存也可能不一样,我们只有在程序处于运行期间时才能知道会创建哪些对象,这部分内存的分配和回收都是动态的。GC 关注的也就是这部分的内存区域。


640?wx_fmt=jpeg


二、GC 的存活标准

知道哪些区域的内存需要被回收之后,我们自然而然地想到了,如何去判断一个对象需要被回收呢?对于如何判断对象是否可以回收,有两种比较经典的判断策略。

  • 引用计数算法
  • 可达性分析算法

1. 引用计数法

在对象头维护着一个 counter 计数器,对象被引用一次则计数器 +1;若引用失效则计数器 -1。当计数器为 0 时,就认为该对象无效了。主流的Java虚拟机里面没有选用引用计数算法来管理内存,其中最主要的原因是它很难解决对象之间相互循环引用的问题。发生循环引用的对象的引用计数永远不会为0,结果这些对象就永远不会被释放。


640?wx_fmt=jpeg


2. 可达性分析算法 

GC Roots 为起点开始向下搜索,搜索所走过的路径称为引用链。当一个对象到GC Roots 没有任何引用链相连时,则证明此对象是不可用的。不可达对象。Java 中,GC Roots 是指:

  • Java 虚拟机栈(栈帧中的本地变量表)中引用的对象
  • 本地方法栈中引用的对象
  • 方法区中常量引用的对象
  • 方法区中类静态属性引用的对象


640?wx_fmt=jpeg


3. Java 中的引用 

Java对引用的概念进行了扩充,将引用分为强引用(Strong Reference)、软引用(Soft Reference)、弱引用(Weak Reference)、虚引用(Phantom Reference)4种,这4种引用强度依次逐渐减弱这样子设计的原因主要是为了描述这样一类对象:当内存空间还足够时,则能保留在内存之中;如果内存空间在进行垃圾收集后还是非常紧张,则可以抛弃这些对象。很多系统的缓存功能都符合这样的应用场景。也就是说,对不同的引用类型,JVM 在进行GC 时会有着不同的执行策略。所以我们也需要去了解一下。

A. 强引用(Strong Reference)

MyClass obj = new MyClass(); // 强引用

obj = null // 此时‘obj’引用被设为null了,前面创建的'MyClass'对象就可以被回收了

只要强引用存在,垃圾收集器永远不会回收被引用的对象,只有当引用被设为null的时候,对象才会被回收。但是,如果我们错误地保持了强引用,比如:赋值给了 static 变量,那么对象在很长一段时间内不会被回收,会产生内存泄漏。

B. 软引用(Soft Reference)

软引用是一种相对强引用弱化一些的引用,可以让对象豁免一些垃圾收集,只有当 JVM 认为内存不足时,才会去试图回收软引用指向的对象。JVM 会确保在抛出 OutOfMemoryError 之前,清理软引用指向的对象软引用通常用来实现内存敏感的缓存,如果还有空闲内存,就可以暂时保留缓存,当内存不足时清理掉,这样就保证了使用缓存的同时,不会耗尽内存。

SoftReference<MyClass> softReference = new SoftReference<>(new MyClass());

C. 弱引用(Weak Reference)

弱引用的强度比软引用更弱一些。当 JVM 进行垃圾回收时,无论内存是否充足,都会回收只被弱引用关联的对象。

WeakReference<MyClass> weakReference = new WeakReference<>(new MyClass());

弱引用可以引申出来一个知识点, WeakHashMap&ReferenceQueueReferenceQueue 是GC回调的知识点。这里因为篇幅原因就不细讲了,推荐引申阅读:ReferenceQueue的使用

D. 幻象引用/虚引用(Phantom References)

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

PhantomReference<MyClass> phantomReference = new PhantomReference<>(new MyClass(), new ReferenceQueue<>());


  • 发表于 2020-01-11 10:07
  • 阅读 ( 524 )
  • 分类:Java开发

你可能感兴趣的文章

相关问题

0 条评论

请先 登录 后评论
Pack
Pack

1135 篇文章

作家榜 »

  1. 轩辕小不懂 2403 文章
  2. 小柒 1312 文章
  3. Pack 1135 文章
  4. Nen 576 文章
  5. 王昭君 209 文章
  6. 文双 71 文章
  7. 小威 64 文章
  8. Cara 36 文章