前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >JVM面试必问:垃圾收集器与内存分配策略

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

作者头像
BUG弄潮儿
发布2020-06-12 16:59:23
4400
发布2020-06-12 16:59:23
举报
文章被收录于专栏:JAVA乐园

阅读文本大概需要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虚拟机》

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2020-01-16,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 BUG弄潮儿 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
相关产品与服务
应用性能监控
应用性能监控(Application Performance Management,APM)是一款应用性能管理平台,基于实时多语言应用探针全量采集技术,为您提供分布式性能分析和故障自检能力。APM 协助您在复杂的业务系统里快速定位性能问题,降低 MTTR(平均故障恢复时间),实时了解并追踪应用性能,提升用户体验。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档