专栏首页JAVA乐园JVM面试必问:垃圾收集器与内存分配策略

JVM面试必问:垃圾收集器与内存分配策略

阅读文本大概需要3分钟。

对象已死?

◆ ◆ ◆ ◆

回收之前需要判断什么对象还“活着”。

1.引用计数法:给对象添加一个引用计数器,有一个地方引用就加1,去除引用就减1。实现简单效率高,但是难以解决循环引用问题,如下图:

如果不下小心直接把 Obj1-reference 和 Obj2-reference 置 null。则在 Java 堆当中的两块内存依然保持着互相引用无法回收。

2.根搜索算法:通过一系列的 ‘GC Roots’ 的对象作为起始点,从这些节点出发所走过的路径称为引用链。当一个对象到 GC Roots 没有任何引用链相连的时候说明对象不可用。

可作为 GC Roots 的对象:

(1)虚拟机栈(栈帧中的本地变量表)中引用的对象

(2)方法区中类静态属性引用的对象

(3)方法区中常量引用的对象

(4)本地方法栈中 JNI(即一般说的 Native 方法) 引用的对象

再谈引用

◆ ◆ ◆ ◆

  1. 强引用:类似于 Object obj = new Object(); 只要强引用在就不回收。
  2. 软引用:SoftReference 类实现软引用。在系统要发生内存溢出异常之前,将会把这些对象列进回收范围之中进行二次回收。
  3. 弱引用:WeakReference 类实现弱引用。对象只能生存到下一次垃圾收集之前。在垃圾收集器工作时,无论内存是否足够都会回收掉只被弱引用关联的对象。
  4. 虚引用:PhantomReference 类实现虚引用。无法通过虚引用获取一个对象的实例,为一个对象设置虚引用关联的唯一目的就是能在这个对象被收集器回收时收到一个系统通知。

关于引用,还可以查看另一篇博文,加深理解:

连阿里HR都知道的四种引用类型,你知道多少!

生存还是死亡

◆ ◆ ◆ ◆

即使在可达性分析算法中不可达的对象,也并非是“非死不可”的,这时候它们暂时处于“缓刑”阶段,一个对象的真正死亡至少要经历两次标记过程:如果对象在进行中可达性分析后发现没有与 GC Roots 相连接的引用链,那他将会被第一次标记并且进行一次筛选,筛选条件是此对象是否有必要执行 finalize() 方法。当对象没有覆盖 finalize() 方法,或者 finalize() 方法已经被虚拟机调用过,虚拟机将这两种情况都视为“没有必要执行”。

如果这个对象被判定为有必要执行 finalize() 方法,那么这个对象竟会放置在一个叫做 F-Queue 的队列中,并在稍后由一个由虚拟机自动建立的、低优先级的 Finalizer 线程去执行它。这里所谓的“执行”是指虚拟机会出发这个方法,并不承诺或等待他运行结束。finalize() 方法是对象逃脱死亡命运的最后一次机会,稍后 GC 将对 F-Queue 中的对象进行第二次小规模的标记,如果对象要在 finalize() 中成功拯救自己 —— 只要重新与引用链上的任何一个对象建立关联即可。

finalize() 方法只会被系统自动调用一次。

垃圾回收区

◆ ◆ ◆ ◆

在堆中,尤其是在新生代中,一次垃圾回收一般可以回收 70% ~ 95% 的空间,而永久代的垃圾收集效率远低于此。 永久代垃圾回收主要两部分内容:废弃的常量和无用的类。

判断废弃常量:一般是判断没有该常量的引用。

判断无用的类:要以下三个条件都满足

(1)该类所有的实例都已经回收,也就是 Java 堆中不存在该类的任何实例

(2)加载该类的 ClassLoader 已经被回收

(3)该类对应的 java.lang.Class 对象没有任何地方被引用,无法在任何地方通过反射访问该类的方法

垃圾回收算法

◆ ◆ ◆ ◆

  1. 标记-清除算法:先标记,再统一回收。有两个缺点:效率不高;产生大量不连续的内部碎片。
  2. 复制算法:把空间分成大小相等的两块,每次只对使用其中的一块。当这块内存使用完时,就将还存活的对象复制到另一块上面,然后将已使用过的内存一次清理。 解决前一种方法的不足,但是会造成空间利用率低下。因为大多数新生代对象都不会熬过第一次 GC。所以没必要 1 : 1 划分空间。可以分一块较大的 Eden 空间和两块较小的 Survivor 空间,每次使用 Eden 空间和其中一块 Survivor。当回收时,将 Eden 和 Survivor 中还存活的对象一次性复制到另一块 Survivor 上,最后清理 Eden 和 Survivor 空间。大小比例一般是 8 : 1 : 1,每次浪费 10% 的 Survivor 空间。但是这里有一个问题就是如果存活的大于 10% 怎么办?这里采用一种分配担保策略:多出来的对象直接进入老年代。
  3. 标记-整理算法:不同于针对新生代的复制算法,针对老年代的特点,创建该算法。主要是先标记,之后把存活对象移到内存的一端,然后清理端边界以外的内存。
  4. 分代收集算法:根据存活对象划分几块内存区,一般是分为新生代和老年代。然后根据各个年代的特点制定相应的回收算法。 新生代:每次垃圾回收都有大量对象死去,只有少量存活,选用复制算法比较合理。 老年代:对象存活率较高、没有额外的空间分配对它进行担保。所以必须使用“标记-清除”或者“标记-整理”算法回收。

垃圾收集器

◆ ◆ ◆ ◆

收集算法是内存回收的理论,而垃圾回收器是内存回收的实践。

1.Serial 收集器:这是一个单线程收集器。意味着它只会使用一个CPU 或一条收集线程去完成收集工作,并且在进行垃圾回收时必须暂停其它所有的工作线程直到收集结束。

2.ParNew 收集器:Serial 收集器的多线程版本

并行Parallel:指多条垃圾收集线程并行工作,此时用户线程处于等待状态

并发Concurrent:指用户线程和垃圾回收线程同时执行(不一定是并行,有可能是交叉执行),用户进程在运行,而垃圾回收线程在另一个 CPU 上运行。

3.Parallel Scavenge 收集器:这是一个新生代收集器,也是使用复制算法实现,同时也是并行的多线程收集器。

CMS 等收集器的关注点是尽可能地缩短垃圾收集时用户线程所停顿的时间,而 Parallel Scavenge 收集器的目的是达到一个可控制的吞吐量(Throughput = 运行用户代码时间 / (运行用户代码时间 + 垃圾收集时间))。作为一个吞吐量优先的收集器,虚拟机会根据当前系统的运行情况收集性能监控信息,动态调整停顿时间。这就是 GC 的自适应调整策略(GC Ergonomics)。

4.Serial Old 收集器:收集器的老年代版本,单线程,使用 标记-整理。

5.Parallel Old 收集器:Parallel Old 是 Parallel Scavenge 收集器的老年代版本。多线程,使用 标记-整理。

6.CMS 收集器:CMS (Concurrent Mark Sweep) 收集器是一种以获取最短回收停顿时间为目标的收集器。基于 标记-清除 算法实现。缺点:对 CPU 资源敏感、无法收集浮动垃圾、标记-清除 算法带来的空间碎片

运作步骤:

(1)初始标记(CMS initial mark):标记 GC Roots 能直接关联到的对象

(2)并发标记(CMS concurrent mark):进行 GC Roots Tracing

(3)重新标记(CMS remark):修正并发标记期间的变动部分

(4)并发清除(CMS concurrent sweep)

7.G1 收集器:面向服务端的垃圾回收器。优点:并行与并发、分代收集、空间整合、可预测停顿。

运作步骤:

(1)初始标记

(2)并发标记

(3)最终标记

(4)筛选回收

内存分配和回收策略

◆ ◆ ◆ ◆

  1. 对象优先在 Eden 分配
  2. 大对象直接进入老年代
  3. 长期存活的对象将进入老年代
  4. 动态对象年龄判定
  5. 空间分配担保

本文参考《深入理解JAVA虚拟机》

本文分享自微信公众号 - JAVA乐园(happyhuangjinjin88)

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2020-01-16

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • Java内存泄漏分析工具Memory Analyzer Tool

    一个大型的Java项目也许从开发到测试结束并可能不能未发现一些重大的问题,但是在生产环境中还是会出现一些非常棘手的问题,如内存泄漏直接导致服务宕机,遇到这样的问...

    java乐园
  • JVM常见面试题

    每个方法执行都会创建一个栈帧,用于存放局部变量表,操作栈,动态链接,方法出口等。每个方法从被调用,直到被执行完。对应着一个栈帧在虚拟机中从入栈到出栈的过程。

    java乐园
  • Kaptcha图片验证码工具

    图片验证码自从诞生以来从未被抛弃,依然发出属于它所应有的光。验证码经常验证如下一些场景。

    java乐园
  • 老生常谈Java虚拟机垃圾回收机制(必看篇)

    程序计数器、虚拟机栈和本地方法栈这三个区域属于线程私有的,只存在于线程的生命周期内,线程结束之后也会消失,因此不需要对这三个区域进行垃圾回收。

    李红
  • 搞定JVM垃圾回收就是这么简单

    当需要排查各种 内存溢出问题、当垃圾收集称为系统达到更高并发的瓶颈时,我们就需要对这些“自动化”的技术实施必要的监控和调节。

    用户2164320
  • JVM垃圾回收(上)

    Java 中的垃圾回收,常常是由 JVM 帮我们做好的。虽然这节省了大家很多的学习的成本,提高了项目的执行效率,但是当项目变得越来越复杂,用户量越来越大时,还是...

    健程之道
  • Android内存管理(垃圾回收算法相关)

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

    Anymarvel
  • 阿里字节跳动90%被问到的JVM面试题

    最近老是收到小伙伴的私信问我能不能帮忙整理出一份JVM相关的面试题出来,说自己在大厂去面试的时候这一块问的是特别多的,每次自己学的时候每次都学不到重点去。这不他...

    烂猪皮
  • Android内存管理(七)垃圾回收算法相关

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

    Anymarvel
  • 垃圾收集策略静态内存分配和回收动态内存分配和回收1 Java堆内存的回收2 回收无效对象的过程3 方法区的内存回收4 垃圾收集算法5 Java中引用的种类

    JavaEdge

扫码关注云+社区

领取腾讯云代金券