面试官不讲武德,竟然问了我18个JVM问题!(一)

简介: GC 对于Java 来说重要性不言而喻,不论是平日里对 JVM 的调优还是面试中的无情轰炸。这篇文章我会以一问一答的方式来展开有关 GC 的内容。本文章所说的 GC 实现没有特殊说明的话,默认指的是 HotSpot 的。我先将十八个问题都列出来,大家可以先思考下能答出几道。

前言

GC 对于Java 来说重要性不言而喻,不论是平日里对 JVM 的调优还是面试中的无情轰炸。

这篇文章我会以一问一答的方式来展开有关 GC 的内容。

本文章所说的 GC 实现没有特殊说明的话,默认指的是 HotSpot 的。

我先将十八个问题都列出来,大家可以先思考下能答出几道。

63.jpg

好了,开始表演。

young gc、old gc、full gc、mixed gc 傻傻分不清?

这个问题的前置条件是你得知道 GC 分代,为什么分代。这个在之前文章提了,不清楚的可以去看看。

现在我们来回答一下这个问题。

其实 GC 分为两大类,分别是 Partial GC 和 Full GC。

Partial GC 即部分收集,分为 young gc、old gc、mixed gc。

  • young gc:指的是单单收集年轻代的 GC。
  • old gc:指的是单单收集老年代的 GC。
  • mixed gc:这个是 G1 收集器特有的,指的是收集整个年轻代和部分老年代的 GC。

Full GC 即整堆回收,指的是收取整个堆,包括年轻代、老年代,如果有永久代的话还包括永久代。

其实还有 Major GC 这个名词,在《深入理解Java虚拟机》中这个名词指代的是单单老年代的 GC,也就是和 old gc 等价的,不过也有很多资料认为其是和 full gc 等价的。

还有 Minor GC,其指的就是年轻代的 gc。

young gc 触发条件是什么?

大致上可以认为在年轻代的 eden 快要被占满的时候会触发 young gc。

为什么要说大致上呢?因为有一些收集器的回收实现是在 full gc 前会让先执行以下 young gc。

比如 Parallel Scavenge,不过有参数可以调整让其不进行 young gc。

可能还有别的实现也有这种操作,不过正常情况下就当做 eden 区快满了即可。

eden 快满的触发因素有两个,一个是为对象分配内存不够,一个是为 TLAB 分配内存不够。

full gc 触发条件有哪些?

这个触发条件稍微有点多,我们来看下。

  • 在要进行 young gc 的时候,根据之前统计数据发现年轻代平均晋升大小比现在老年代剩余空间要大,那就会触发 full gc。
  • 有永久代的话如果永久代满了也会触发 full gc。
  • 老年代空间不足,大对象直接在老年代申请分配,如果此时老年代空间不足则会触发 full gc。
  • 担保失败即 promotion failure,新生代的 to 区放不下从 eden 和 from 拷贝过来对象,或者新生代对象 gc 年龄到达阈值需要晋升这两种情况,老年代如果放不下的话都会触发 full gc。
  • 执行 System.gc()、jmap -dump 等命令会触发 full gc。

知道 TLAB 吗?来说说看

这个得从内存申请说起。

一般而言生成对象需要向堆中的新生代申请内存空间,而堆又是全局共享的,像新生代内存又是规整的,是通过一个指针来划分的。

image.gif64.jpg

内存是紧凑的,新对象创建指针就右移对象大小 size 即可,这叫指针加法(bump [up] the pointer)。

可想而知如果多个线程都在分配对象,那么这个指针就会成为热点资源,需要互斥那分配的效率就低了。

于是搞了个 TLAB(Thread Local Allocation Buffer),为一个线程分配的内存申请区域。

这个区域只允许这一个线程申请分配对象,允许所有线程访问这块内存区域

TLAB 的思想其实很简单,就是划一块区域给一个线程,这样每个线程只需要在自己的那亩地申请对象内存,不需要争抢热点指针。

当这块内存用完了之后再去申请即可。

这种思想其实很常见,比如分布式发号器,每次不会一个一个号的取,会取一批号,用完之后再去申请一批。

65.jpg

可以看到每个线程有自己的一块内存分配区域,短一点的箭头代表 TLAB 内部的分配指针。

如果这块区域用完了再去申请即可。

不过每次申请的大小不固定,会根据该线程启动到现在的历史信息来调整,比如这个线程一直在分配内存那么 TLAB 就大一些,如果这个线程基本上不会申请分配内存那 TLAB 就小一些。

还有 TLAB 会浪费空间,我们来看下这个图。

66.jpg

可以看到 TLAB 内部只剩一格大小,申请的对象需要两格,这时候需要再申请一块 TLAB ,之前的那一格就浪费了。

在 HotSpot 中会生成一个填充对象来填满这一块,因为堆需要线性遍历,遍历的流程是通过对象头得知对象的大小,然后跳过这个大小就能找到下一个对象,所以不能有空洞。

当然也可以通过空闲链表等外部记录方式来实现遍历。

还有 TLAB 只能分配小对象,大的对象还是需要在共享的 eden 区分配

所以总的来说 TLAB 是为了避免对象分配时的竞争而设计的。

那 PLAB 知道吗?

可以看到和 TLAB 很像,PLAB 即 Promotion Local Allocation Buffers。

用在年轻代对象晋升到老年代时。

在多线程并行执行 YGC 时,可能有很多对象需要晋升到老年代,此时老年代的指针就“热”起来了,于是搞了个 PLAB。

先从老年代 freelist(空闲链表) 申请一块空间,然后在这一块空间中就可以通过指针加法(bump the pointer)来分配内存,这样对 freelist 竞争也少了,分配空间也快了。

67.jpg

大致就是上图这么个思想,每个线程先申请一块作为 PLAB ,然后在这一块内存里面分配晋升的对象。

这和 TLAB 的思想相似。

产生 concurrent mode failure 真正的原因

《深入理解Java虚拟机》:由于CMS收集器无法处理“浮动垃圾”(FloatingGarbage),有可能出现“Con-current Mode Failure”失败进而导致另一次完全“Stop The World”的Full GC的产生。

这段话的意思是因为抛这个错而导致一次 Full GC。

实际上是 Full GC 导致抛这个错,我们来看一下源码,版本是 openjdk-8。

首先搜一下这个错。

image.gif

再找找看  report_concurrent_mode_interruption 被谁调用。

查到是在 void CMSCollector::acquire_control_and_collect(...) 这个方法中被调用的。

68.jpg

再来看看 first_state :CollectorState first_state = _collectorState;

69.jpg

看枚举已经很清楚了,就是在 cms gc 还没结束的时候。

acquire_control_and_collect 这个方法是 cms 执行 foreground gc 的。

cms 分为  foreground gc 和 background gc。

foreground 其实就是 Full gc。

因此是 full gc 的时候 cms gc 还在进行中导致抛这个错

究其原因是因为分配速率太快导致堆不够用,回收不过来因此产生 full gc。

也有可能是发起 cms gc 设置的堆的阈值太高。

CMS GC 发生 concurrent mode failure 时的 full GC 为什么是单线程的?

以下的回答来自 R 大

因为没足够开发资源,偷懒了。就这么简单。没有任何技术上的问题。大公司都自己内部做了优化。

所以最初怎么会偷这个懒的呢?多灾多难的CMS GC经历了多次动荡。它最初是作为Sun Labs的Exact VM的低延迟GC而设计实现的。

但 Exact VM在与 HotSpot VM争抢 Sun 的正牌 JVM 的内部斗争中失利,CMS GC 后来就作为 Exact VM 的技术遗产被移植到了 HotSpot VM上。

就在这个移植还在进行中的时候,Sun 已经开始略显疲态;到 CMS GC 完全移植到 HotSpot VM 的时候,Sun 已经处于快要不行的阶段了。

开发资源减少,开发人员流失,当时的 HotSpot VM 开发组能够做的事情并不多,只能挑重要的来做。而这个时候 Sun Labs 的另一个 GC 实现,Garbage-First GC(G1 GC)已经面世。

相比可能在长时间运行后受碎片化影响的 CMS,G1 会增量式的整理/压缩堆里的数据,避免受碎片化影响,因而被认为更具潜力。

于是当时本来就不多的开发资源,一部分还投给了把G1 GC产品化的项目上——结果也是进展缓慢。

毕竟只有一两个人在做。所以当时就没能有足够开发资源去打磨 CMS GC 的各种配套设施的细节,配套的备份 full GC 的并行化也就耽搁了下来。

但肯定会有同学抱有疑问:HotSpot VM不是已经有并行GC了么?而且还有好几个?

让我们来看看:

  • ParNew:并行的young gen GC,不负责收集old gen。
  • Parallel GC(ParallelScavenge):并行的young gen GC,与ParNew相似但不兼容;同样不负责收集old gen。
  • ParallelOld GC(PSCompact):并行的full GC,但与ParNew / CMS不兼容。

所以…就是这么一回事。

HotSpot VM 确实是已经有并行 GC 了,但两个是只负责在 young GC 时收集 young gen 的,这俩之中还只有 ParNew 能跟 CMS 搭配使用;

而并行 full GC 虽然有一个 ParallelOld,但却与 CMS GC 不兼容所以无法作为它的备份 full GC使用。

为什么有些新老年代的收集器不能组合使用比如 ParNew 和 Parallel Old?

70.jpg

这张图是 2008 年 HostSpot 一位 GC 组成员画的,那时候 G1 还没问世,在研发中,所以画了个问号在上面。

里面的回答是 :

"ParNew" is written in a style... "Parallel Old" is not written in the "ParNew" style

HotSpot VM 自身的分代收集器实现有一套框架,只有在框架内的实现才能互相搭配使用。

而有个开发他不想按照这个框架实现,自己写了个,测试的成绩还不错后来被  HotSpot VM 给吸收了,这就导致了不兼容。

我之前看到一个回答解释的很形象:就像动车组车头带不了绿皮车厢一样,电气,挂钩啥的都不匹配。

相关文章
|
8月前
|
存储 监控 算法
阿里面试官,别挂电话,jvm性能调优,我还能说上半小时
性能调优:性能调优包含多个层次,比如:架构调优、代码调优、JVM调优、数据库调优、操作系统调优等。架构调优和代码调优是JVM调优的基础,其中架构调优是对系统影响最大的。性能调优基本上按照以下步骤进行:明确优化目标、发现性能瓶颈、性能调优、通过监控及数据统计工具获得数据、确认是否达到目标。
70 0
|
7月前
|
Java 开发者
吊打面试官的16000字JVM专属秘籍,又一个Java面试神器!
前言 吊打面试官的16000字JVM专属秘籍,总共包含三部分的内容,从基础到进阶带大家一步步深入理解JVM! 学完就可以在简历上面直接写上精通JVM!
|
8月前
|
前端开发 Java 程序员
阿里p8 面试官狂推的java面试神器!jvm与多线程面试80问!
说在前面的话 网上各种关于Java太卷的说法很对,Java目前是越来越卷了,但“卷”对个人来说也不一定是坏事,我们得搞清楚Java越来越卷的底层逻辑,才能客观看待这个事。
|
8月前
|
Java 关系型数据库 MySQL
阿里面试官(性能优化):描述一下jvm加载class文件的原理机制?
相信很多人对于性能优化都不陌生,为了获得更好的系统性能,或者是为了满足不断增加的业务需求。 都需要用到我们的性能调优。所以性能优化在面试中出现的频率特别高 楼主自认为自己对性能优化相关知识有很多了解,而且因为最近在找工作面试,所以单独复习了很多关于索引的知识。
|
8月前
|
缓存 算法 Oracle
面试官:JVM是如何判定对象已死的?学JVM必会的知识!
作为一名Java程序员,我们每天都在程序里不停地去new对象,但是你知道这些被new出来的对象,最后是怎么被回收的吗?
22221 4
面试官:JVM是如何判定对象已死的?学JVM必会的知识!
|
9月前
|
缓存 算法 NoSQL
面试官不讲武德:小伙子来,你先给我把限流讲清楚
随着微服务的流行,服务和服务之间的稳定性变得越来越重要。缓存、降级和限流是保护微服务系统运行稳定性的三大利器。 缓存:提升系统访问速度和增大系统能处理的容量 降级:当服务出问题或者影响到核心流程的性能则需要暂时屏蔽掉 限流:解决服务雪崩,级联服务发生阻塞时,及时熔断,防止请求堆积消耗占用系统的线程、IO等资源,造成其他级联服务所在服务器的崩溃
|
缓存 算法 Java
面试官:JVM是如何判定对象已死的?
在堆里面存放着Java世界中几乎所有的对象实例,垃圾收集器在对堆进行回收前,第一件事情就是要确定这些对象之中哪些还“存活”着,哪些已经“死去”(“死去”即不可能再被任何途径使用的对象)了。
164 0
|
Java 数据库 Android开发
JVM 系列(6)吊打面试官:为什么 finalize() 方法只会执行一次?
JVM 系列(6)吊打面试官:为什么 finalize() 方法只会执行一次?
141 0
JVM 系列(6)吊打面试官:为什么 finalize() 方法只会执行一次?
|
存储 缓存 算法
JVM 系列(5)吊打面试官:说一下 Java 的四种引用类型
JVM 系列(5)吊打面试官:说一下 Java 的四种引用类型
177 0
JVM 系列(5)吊打面试官:说一下 Java 的四种引用类型
|
存储 算法 Java
JVM 系列(4)吊打面试官:对象的内存分为哪几个部分?
JVM 系列(4)吊打面试官:对象的内存分为哪几个部分?
111 0
JVM 系列(4)吊打面试官:对象的内存分为哪几个部分?