在上一篇介绍了 Java 虚拟机内存的运行时数据区。本章将会介绍 Java 中的垃圾收集算法与常用的垃圾收集器。

在涉及 Java 相关的面试中,面试官经常会让讲讲 Java 中的垃圾收集相关的理解和常见的分类。可见,光就应付面试而言,JVM 的垃圾收集也对每一位 Java 开发者很重要。除此之外,对于我们了解和解决 Java 应用的性能时,也很有帮助。

本文的主要内容:

  • Java 中的引用

    • 强引用
    • 软引用
    • 弱引用
    • 虚引用
  • 对象回收
    • 引用计数法
    • 可达性分析算法
  • 垃圾收集算法
    • 标记-清除算法
    • 标记-整理算法
    • 复制算法
    • 分代收集算法
  • 垃圾收集器
  • 小结

Java 中的引用

判定对象是否存活都与引用有关。在Java语言中,将引用又分为强引用、软引用、弱引用和虚引用 4 种,这四种引用强度依次逐渐减弱。下面我们一次介绍这四种引用。

强引用

在程序中普遍存在的,类似 Object obj = new Object() 这类引用。只要强引用存在,垃圾收集器永远不会回收掉被引用的对象。

软引用

描述一些还有用但并非必须的对象。在 Java 中用 java.lang.ref.SoftReference 类来表示。对于软引用关联着的对象,在系统将要发生内存溢出异常之前,将会把这些对象列进回收范围之中进行第二次回收。如果这次回收后还没有足够的内存,才会抛出内存溢出异常。SoftReference 类有两个构造函数:

    public SoftReference(T referent) {super(referent);this.timestamp = clock;}public SoftReference(T referent, ReferenceQueue<? super T> q) {super(referent, q);this.timestamp = clock;}

软引用可以和一个引用队列(ReferenceQueue)联合使用,如果软引用所引用的对象被JVM回收,这个软引用就会被加入到与之关联的引用队列中。下面是 SoftReference 一个使用示例:

```javapublic static void main(String[] args) throws InterruptedException {SoftReference<String> sr = new SoftReference<>("hello");System.out.println(sr.get());
}
```

弱引用

描述非必须的对象,Java 中常通过使用弱引用来避免内存泄漏。被弱引用关联的对象只能生存到下一次垃圾收集发生之前。当垃圾收集器工作时,无论当前内存是否足够,都会回收掉只被弱引用关联的对象。

    public WeakReference(T referent) {super(referent);}public WeakReference(T referent, ReferenceQueue<? super T> q) {super(referent, q);}
    public static void main(String[] args) throws InterruptedException {WeakReference<String> wr = new WeakReference<>("hello");System.out.println(wr.get());System.gc();                //通知JVM的gc进行垃圾回收Thread.sleep(1000);System.out.println(wr.get());
}

同样,通过引用队列这个参数,我们便把创建的弱引用对象注册到了一个引用队列上,这样当它被垃圾回收器清除时,就会把它送入这个引用队列中,我们便可以对这些被清除的弱引用对象进行统一管理。

软引用和弱引用的区别在于,若一个对象是弱引用可达,无论当前内存是否充足它都会被回收,而软引用可达的对象在内存不充足时才会被回收,因此软引用要比弱引用强一些。

虚引用

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

    public static void main(String[] args) {ReferenceQueue<String> queue = new ReferenceQueue<>();PhantomReference<String> pr = new PhantomReference<>("hello", queue);System.out.println(pr.get());}

要注意的是,虚引用必须和引用队列关联使用,当垃圾回收器准备回收一个对象时,如果发现它还有虚引用,就会把这个虚引用加入到与之 关联的引用队列中。程序可以通过判断引用队列中是否已经加入了虚引用,来了解被引用的对象是否将要被垃圾回收。如果程序发现某个虚引用已经被加入到引用队列,那么就可以在所引用的对象的内存被回收之前采取必要的行动。

对象回收

堆中几乎放着所有的对象实例,对堆垃圾回收前的第一步就是要判断那些对象已经死亡(即不能再被任何途径使用的对象)。

引用计数法

给对象中添加一个引用计数器,每当有一个地方引用它,计数器就加 1;当引用失效,计数器就减 1;任何时候计数器为 0 的对象就是不可能再被使用的。

引用计数法可以很快的执行,交织在程序运行中。对程序需要不被长时间打断的实时环境比较有利。实现简单,效率高,但是目前主流的虚拟机中并没有选择这个算法来管理内存,其最主要的原因是它很难解决对象之间相互循环引用的问题。如父对象有一个对子对象的引用,子对象反过来引用父对象。这样,他们的引用计数永远不可能为 0。

可达性分析算法

这个算法的基本思想就是通过一系列的称为 GC Roots 的对象作为起点,从这些节点开始向下搜索,节点所走过的路径称为引用链,当一个对象到 GC Roots 没有任何引用链相连的话,则证明此对象是不可用的。

在 Java 中哪些对象可以成为 GC Root?

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

垃圾收集算法

常用的垃圾收集算法有:标记-清除、标记-整理、复制和分代收集算法。下面依次介绍这几种垃圾收集算法。

标记-清除算法

首先标记出需要回收的对象,在标记完成后统一回收掉所有的被标记对象。

缺点:效率问题和空间问题(标记清除后会产生大量的不连续内存碎片,内存碎片过多可能会导致程序需要分配较大对象时找不到足够大的连续内存空间而不得不提前触发另一次垃圾回收动作)

标记-整理算法

标记-整理 算法采用 标记-清除 算法一样的方式进行对象的标记,但在清除时不同,在回收不存活的对象占用的空间后,会将所有的存活对象往左端空闲空间移动,并更新对应的指针。标记-整理算法是在标记-清除算法的基础上,又进行了对象的移动,因此成本更高,但是却解决了内存碎片的问题。

复制算法

将内存划分为大小相等的两块,每次只使用其中的一块。当这块内存用完了,就将还存活的对象复制到另一块内存上,然后把已使用过的内存空间一次清理掉。

复制算法的提出是为了克服句柄的开销和解决内存碎片的问题。每次只对其中一块进行GC,不用考虑内存碎片的问题,并且实现简单,运行高效。缺点是内存缩小了一半。

分代收集算法

分代收集算法是目前大部分JVM的垃圾收集器采用的算法。它的核心思想是根据对象存活的生命周期将内存划分为若干个不同的区域。一般情况下将堆区划分为老年代(Tenured Generation)和新生代(Young Generation),在堆区之外还有一个代就是永久代(Permanet Generation)。老年代的特点是每次垃圾收集时只有少量对象需要被回收,而新生代的特点是每次垃圾回收时都有大量的对象需要被回收,那么就可以根据不同代的特点采取最适合的收集算法。

年轻代(Young Generation)的回收算法

所有新生成的对象首先都是放在年轻代的。年轻代的目标就是尽可能快速的收集掉那些生命周期短的对象。

新生代内存按照 8:1:1 的比例分为一个 Eden 区和两个 survivor(survivor0,survivor1) 区。一个E den 区,两个 Survivor 区(一般而言)。大部分对象在 Eden 区中生成。回收时先将 Eden 区存活对象复制到一个 survivor0 区,然后清空 Eden 区,当这个 survivor0 区也存放满了时,则将 Eden 区和 survivor0 区存活对象复制到另一个 survivor1 区,然后清空 Eden 和这个 survivor0 区,此时 survivor0 区是空的,然后将 survivor0 区和 survivor1 区交换,即保持 survivor1 区为空, 如此往复。

当 survivor1 区不足以存放 Eden 和 survivor0 的存活对象时,就将存活对象直接存放到老年代。若是老年代也满了就会触发一次 Full GC,也就是新生代、老年代都进行回收。

新生代发生的 GC 也叫做 Minor GC,Minor GC 发生频率比较高(不一定等 Eden 区满了才触发)。

年老代(Old Generation)的回收算法

在年轻代中经历了N次垃圾回收后仍然存活的对象,就会被放到年老代中。因此,可以认为年老代中存放的都是一些生命周期较长的对象。

内存比新生代也大很多(大概比例是 1:2),当老年代内存满时触发 Major GC 即 Full GC,Full GC 发生频率比较低,老年代对象存活时间比较长,存活率标记高。

持久代(Permanent Generation)的回收算法

用于存放静态文件,如 Java 类、方法等。持久代对垃圾回收没有显著影响,但是有些应用可能动态生成或者调用一些 class,例如 Hibernate 等,在这种时候需要设置一个比较大的持久代空间来存放这些运行过程中新增的类。持久代也称方法区,方法区存储内容是否需要回收的判断不一样。方法区主要回收的内容有:废弃常量和无用的类。对于废弃常量也可通过引用的可达性来判断,但是对于无用的类则需要同时满足下面 3 个条件:

  • 该类所有的实例都已经被回收,也就是 Java 堆中不存在该类的任何实例;
  • 加载该类的 ClassLoader 已经被回收;
  • 该类对应的 java.lang.Class 对象没有在任何地方被引用,无法在任何地方通过反射访问该类的方法。

垃圾收集器

不同的垃圾回收器,适用于不同的场景。常用的垃圾回收器:

  • 串行(Serial)回收器是单线程的一个回收器,简单、易实现、效率高。
  • 并行(ParNew)回收器是Serial的多线程版,可以充分的利用CPU资源,减少回收的时间。
  • 吞吐量优先(Parallel Scavenge)回收器,侧重于吞吐量的控制。
  • 并发标记清除(CMS,Concurrent Mark Sweep)回收器是一种以获取最短回收停顿时间为目标的回收器,该回收器是基于 标记-清除 算法实现的。

小结

本文讲了 JVM 垃圾收集中涉及的四种对象引用的类型:强引用、软引用、弱引用和虚引用,对象死亡的判断算法:引用计数法和可达性分析。最后介绍了几种常见的垃圾收集算法。关于具体的垃圾回收器将在下一篇文章具体介绍。

订阅最新文章,欢迎关注我的公众号

最全的 JVM 面试知识点(二):垃圾收集相关推荐

  1. 最全的 JVM 面试知识点(一):运行时数据区

    转自: https://blog.csdn.net/keets1992/article/details/92089754 不是码农,不会敲代码的她,却最懂程序员!| 人物志: https://blog ...

  2. JVM面试知识点合集 — Android 春招 2022

    JVM面试知识点合集 - Android 春招 2022 星光不问赶路人,时间不负有心人 Tips:文章较长,可以在侧栏目点击子标题,快速跳转 喜欢的话,就一键三连吧

  3. 满满的一整篇,全是 JVM 核心知识点!

    头图 | CSDN 下载自视觉中国 作者 | sowhat1412  责编 | 张文 来源 | sowhat1412(ID:sowhat9094) 想要提高程序员自身的内功心法无非就是数据结构跟算法 ...

  4. 最全的TCP面试知识点

    TCP硬核面试题!35 张图解被问千百遍的 TCP 三次握手和四次挥手面试题 前言 不管面试 Java .C/C++.Python 等开发岗位, TCP 的知识点可以说是的必问的了. 任 TCP 虐我 ...

  5. 全面的html面试知识点

    文章目录 @[toc] 1. HTML.XML.XHTML 的区别 2. 什么是HTML5以及和HTML的区别是什么 概念 区别 3. HTML.XHTML和HTML5区别以及有什么联系 XHTML与 ...

  6. 一文掌握 JVM 面试要点

    之前发表的「吃透MySQL系列」专栏与「吃透Redis系列」专栏收到很多小伙伴的来信,回馈效果都很好.但是反应关于JVM的文章很少. 因此,我打算开一个「吃透JVM系列」的专栏. 之前发过一篇关于JV ...

  7. Java 面试知识点解析(三)——JVM篇

    前言: 在遨游了一番 Java Web 的世界之后,发现了自己的一些缺失,所以就着一篇深度好文:知名互联网公司校招 Java 开发岗面试知识点解析 ,来好好的对 Java 知识点进行复习和学习一番,大 ...

  8. 二位四进制计数器_金三银四还在看JVM这一块?看完这篇万字JVM面试解析就够了...

    金三银四你必备的学习笔记 Java面试核心知识点笔记+高级架构面试知识点整理+互联网Java工程师必备的1080道面试解析​shimo.im Java内存区域 说一下 JVM 的主要组成部分及其作用? ...

  9. Java 面试全解析:核心知识点与典型面试题

    课程背景 又逢"金九银十",年轻的毕业生们满怀希望与忐忑,去寻找.竞争一个工作机会.已经在职的开发同学,也想通过社会招聘或者内推的时机争取到更好的待遇.更大的平台. 然而,面试人群 ...

最新文章

  1. 设置RGBColor
  2. 小程序 缩放_缩放流星应用程序的初体验
  3. 在Scrapy中使用Chrome中的cookie
  4. 设计模式——控制反转依赖注入
  5. ajax提交form表单数据_[基础编程学习] [PHP7数组详解]:第2章 (1)从表单提交数据说起...
  6. 如何将ClearCase集成进VS.NET 2003的IDE
  7. Postgresql数据库主从备份教程
  8. NSTimer不准确与GCDTimer详解
  9. z-wave问题汇总
  10. 给 JDK 报了一个 P4 的 Bug,结果居然……
  11. hive:导出数据记录中null被替换为\n的解决方案
  12. cdrx7拼版工具在哪里_CorelDRAW X7标签怎么排版?
  13. R查看和更改工作路径的命令
  14. 魔兽世界私服架设 服务器架设简易教程
  15. Docker常见错误
  16. 关于一些Excel的快捷键总结
  17. 学习笔记28(凹凸贴图,法线贴图,位移贴图)
  18. 最简单的改变字体大小代码
  19. 收藏--真正爱你的男人
  20. php写入文件内容方法,学习php写入文件内容的方法

热门文章

  1. 双显示器扩展显示时怎么移动鼠标到另一块屏?
  2. 【计算机毕业设计】基于微信小程序的图书馆座位预约系统
  3. cad零点坐标标注lisp_CAD_XY坐标标注AUTO_LISP程序
  4. Java:Java实现简单闹钟设计
  5. P1413 坚果保龄球洛谷c++题解
  6. mac版eclipse连接mysql_将Eclipse连接到mysql mac os x jdbc驱动程序
  7. 现代企业管理的12个指南针
  8. STM32工程文件的建立以及Keil软件的基本设置和修改
  9. Docker系列技术分享、容器技术和Docker
  10. primeNG__datatable