面试官超级喜欢问的JVM

5,574 阅读8分钟

前言

随着阿巴阿巴在面试中愈战愈勇,这几天又约上面试了,这次面试官让她谈谈对JVM的理解。

回家等通知

面试官: 你对JVM的内存模型了解吗?能否讲讲里面的细节呢?

阿巴阿巴: JAVA虚拟机在执行JAVA程序的过程中,会把所有它管理的内存划分为若干个不同的数据区域,这些区域都有着各自的用途。

根据《JAVA 虚拟机规范SE 7 版》的规定,JAVA虚拟机所管理的内存将包括以下几个运行时的数据区域:堆、方法区、虚拟机栈、本地方法栈、程序计数器。

面试官: 那你能给我大概的介绍下这几区域吗?

阿巴阿巴: 方法区主要存放的是虚拟机加载的类信息、常量、静态变量,堆区域主要是存放对象的,虚拟机栈是用来存放方法运行时产生的栈帧的,本地方法栈则是用来存放本地方法(native)方法运行时产生的栈帧的,程序计数器是用于存放下一条指令所在单元的地址的地方。

面试官: 不错,那你知道什么时候栈内存会发生溢出嘛?

阿巴阿巴: 嗯,,,如果线程的栈深度大于虚拟机运行的最大深度,将抛出StackOverflowError异常,常常出现于递归的方法调用。

面试官: 那还有其他情况会出现栈内存溢出嘛?

阿巴阿巴: 好像没有其他情况了......

面试官: 堆的分代可以细讲一下嘛?

阿巴阿巴: 堆的话主要进行了一个分代,分成新生代、老年代、持久代。

阿巴阿巴: 新生代主要存一些朝生夕死的对象,老年代存的是比较稳定的对象或者是大对象,持久代用于存放用于存放静态文件,如今Java类、方法等。

面试官: 好的、那今天的面试就到这里吧,你先回去等通知哈。😈

阿巴阿巴: 好的。

当场拿offer

阿巴阿巴: 你对JVM的内存模型了解吗?能否讲讲里面的细节呢?

阿巴阿巴: JAVA虚拟机在执行JAVA程序的过程中,会把所有它管理的内存划分为若干个不同的数据区域,这些区域都有着各自的用途。

根据《JAVA 虚拟机规范SE 7 版》的规定,JAVA虚拟机所管理的内存将包括以下几个运行时的数据区域:堆、方法区、虚拟机栈、本地方法栈、程序计数器。

面试官: 那你能给我大概的介绍下这几区域吗?

阿巴阿巴: 方法区主要存放的是虚拟机加载的类信息、常量、静态变量,堆区域主要是存放对象的,虚拟机栈是用来存放方法运行时产生的栈帧的,本地方法栈则是用来存放本地方法(native)方法运行时产生的栈帧的,程序计数器是用于存放下一条指令所在单元的地址的地方。

面试官: 不错,那你知道什么时候栈内存会发生溢出嘛?

阿巴阿巴: 有2种情况,第一种情况:如果线程的栈深度大于虚拟机运行的最大深度,将抛出StackOverflowError异常,常常出现于递归的方法调用。还有一种情况:如果不停的创建线程,这样每个线程都会占用一定的空间,最后导致栈空间不足,报OOM异常。JVM的这几个区域里,堆、方法区、本地方法栈、虚拟机栈都有可能会出现OOM的一个异常。而程序计数器是唯一一个不会产生OOM的区域。

面试官: 可以啊,那再讲讲堆的分布呗!

阿巴阿巴: 堆主要分为新生代、老年代和持久代,这个持久代在JDK1.8版本的时候已经划出了堆内存了,使用元空间来替代,元空间已经不在JVM中,使用的是本地内存。

阿巴阿巴: 新生代主要存一些朝生夕死的对象,老年代存的是比较稳定的对象或者是大对象,持久代用于存放用于存放静态文件,如今Java类、方法等。

阿巴阿巴: 新生代的分区。

阿巴阿巴: 新生代分成Eden区和survivor区,其中survivor区又分为(s0和s1)俩个区域,它们的比例如图所示为8 : 1 : 1。新对象优先会在Eden区进行分配,如果是大对象那就直接进入老年代,当Eden区发生GC时,幸存的对象就会转入Survivor区,当在Survivor区中的对象的年龄达到设定的阈值时(默认是15),达到后就会被转入到老年代区域中。

面试官: 哪些区域是线程共享的,哪些区域是线程私有的呢?

阿巴阿巴: 方法区和堆区是线程共享的,虚拟机栈、本地方法栈、程序计数器这三个区域是线程私有的。

面试官: 既然堆事共享的,那么新生成对象时,堆里面的内存空间就有可能被多线程进行竞争,JVM在这一块是如何保证线程安全的呢?

阿巴阿巴: 这个嘛,JVM采用了2种方式来处理,1使用的是指针碰撞,即CAS进行内存空间的竞争,而且JVM还会维护一个空闲列表,来快速找到空闲的内存。同时JVM还会采用TLAB,Thread Local Allocation Buffer,即线程本地分配缓存区的方式,将Eden区的部分内存让线程私有,当需要创建新对象的时候使用这部分线程私有的空间来分配给对象,这样可以减少竞争带来的额外开销。

面试官: 那对象都是分配到堆里的嘛?

阿巴阿巴: 绝大多数对象都是分配在堆里面的,但是也有特殊情况,可以将对象在栈上分配,如果想要将对象的分配到栈上,那么就需要进行逃逸分析。

阿巴阿巴: 逃逸分析的基本行为就是分析对象的作用域,当一个对象在方法中被定义后,如果这个对象被引用到方法的外部,或者能被其他线程给访问到,那么称这个对象逃逸了。

阿巴阿巴: 如果确定了一个对象不会逃逸出方法之外,我们就可以将这个对象的内存分配到栈上,那么这个对象的内存空间就可以随着栈帧的的生命周期而变化,一起创建一起消亡,这样可以减少堆的负担,提高GC的性能,从而提高程序的运行效率。

阿巴阿巴: 但是逃逸分析默认是不打开的,因为逃逸分析需要对对象的作用域进行大量的计算,从而来判断对象是否会逃逸,而这种计算又会对性能造成影响,所以栈上分配实现起来比较复杂,一般不使用。用户可以使用参数 -XX : +DoEsapeAnalysis来手动开启逃逸分析。

面试官: 你对内空间配担保这块熟悉吗?

面试官: 空间分配担保一般出现在Minor GC的时候,Minor GC后会将符合条件的对象转移到老年代,如果老年代最大可用的连续空间大于新生代的所有对象的所占空间总和,那么这次Minor GC将会被认为是安全的,如果小于,那么JVM则会查看Handle配置HandlePromotionFailure设置值是否允许担保失败。如果HandlePromotionFailure=true,那么会继续检查老年代最大可用连续空间是否大于历次晋升到老年代的对象的平均大小,如果大于,则尝试进行一次Minor GC,但这次Minor GC依然是有风险的;如果小于或者HandlePromotionFailure=false,则改为进行一次Full GC。

面试官: 有了解过GC算法和垃圾回收器吗?

阿巴阿巴: 有的,GC算法主要分为下面这几类(滴滴滴。。。这时候面试官的电话响了) 面试官: 要不今天先这样,面试通过了,明天过来二面吧~

阿巴阿巴: 好哒。

总结

分享了一波JVM相关的高频面试题,了解JVM的一个模型机内部的一些基本机制,包括对象内存的分配,线程私有和共享相关的问题。

小巴由此开启了JVM相关的面试之旅,于是她回家天天恶补关于JVM的知识。

❤️/ 感谢支持 /

以上便是本次分享的全部内容,希望对你有所帮助^_^

喜欢的话别忘了 分享、点赞、收藏 三连哦~

欢迎关注公众号 程序员巴士,来自字节、虾皮、招银的三端兄弟,分享编程经验、技术干货与职业规划,助你少走弯路进大厂。