面试题JVM篇-10道常见面试题
Posted LL.LEBRON
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了面试题JVM篇-10道常见面试题相关的知识,希望对你有一定的参考价值。
文章目录
【面试题】JVM篇-10道常见面试题
本文章参考:
注意:如果本文中有错误的地方,欢迎评论区指正!🍭
- 备战实习,会定期的总结常考的面试题,大家一起加油!🎯
1.说一下JVM的内存整体的结构(运行时数据区)和各自的功能?
可以按线程是否私有分为两大类:
- 线程私有的有程序计数器、虚拟机栈、本地方法栈
- 线程共享的有堆、方法区
程序计数器:是一块较小的内存空间,它可以看作是当前线程所执行的字节码的行号指示器。用于保存JVM中下一条所要执行的指令的地址
此区域是唯一一个虚拟机规范中没有规定任何OutOfMemoryError
情况的区域
Java虚拟机栈:是每个线程运行需要的内存空间,每个方法在执行的同时都会创建一个帧栈(Stack Frame)用于存储局部变量表、操作数栈、动态链接、方法出口等信息
Java虚拟机栈有两种异常状况︰如果线程请求的栈的深度大于虚拟机所允许的深度将抛出StackOverflowError
异常;如果扩展时无法申请到足够的内存,就会抛出OutOfMemoryError
异常。
本地方法栈:是一些带有native关键字的方法就是需要JAVA去调用本地的C或者C++方法,因为JAVA有时候没法直接和操作系统底层交互,所以需要用到本地方法
与Java虚拟机栈一样,本地方法栈也会抛出StackOverflowError
和 OutOfMemoryError
异常
Java堆:是被所有线程所共享的一块内存区域,在虚拟机启动时创建。此内存区域的唯一目的就是存放对象实例,几乎所有的对象实例都在这里分配内存。也就是说通过new关键字创建的对象都会使用堆内存
如果在堆中没有完成实例分配。并且堆也无法扩展时,将会抛出 OutOfMemoryError 异常
方法区:和Java堆一样,是各个线程共享的内存区域,它用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据
当方法区无法满足内存分配需求时,将抛出OutOfMemoryError
异常
面试官追问:Java7和Java8中方法区有啥不同?
方法区只是JVM规范中定义的一个概念,并没有规定如何去实现它,不同的厂商有不同的实现。而永久代是Java7中Hotspot 虚拟机特有的概念,Java8的时候又被元空间取代了,永久代和元空间都可以理解为方法区的落地实现。两者不同的是
- 永久代用的堆内存
- 元空间用的本地内存
2.说一下如何判断一个对象是否可以回收?
有两种算法,引用计数法和可达性分析算法
引用计数法就是给对象添加一个引用计数器,当对象增加一个引用时计数器加1,引用失效时计数器减1。引用计数为0的对象可被回收
但是它有个缺点,会出现循环引用的情况,两个对象互相引用,因此 Java 虚拟机不使用引用计数算法
可达性分析算法是通过GC Roots 作为起始点进行搜索,能够到达到的对象都是存活的,不可达的对象可被回收。
面试官又问:在Java中GC Roots一般包含哪些?
- 虚拟机栈中引用的对象
- 本地方法栈中引用的对象
- 方法区中类静态属性引用的对象
- 方法区中的常量引用的对象
3.谈谈对 Java 中引用的了解?
Java 具有四种强度不同的引用类型
强引用:被强引用关联的对象不会被回收。可以使用 new 一个新对象的方式来创建强引用
Object obj = new Object();
软引用:被软引用关联的对象只有在内存不够的情况下才会被回收。使用 SoftReference
类来创建软引用
Object obj = new Object();
SoftReference<Object> sf = new SoftReference<Object>(obj);
obj = null; // 使对象只被软引用关联
弱引用:被弱引用关联的对象一定会被回收,也就是说它只能存活到下一次垃圾回收发生之前。使用 WeakReference
类来实现弱引用
Object obj = new Object();
WeakReference<Object> wf = new WeakReference<Object>(obj);
obj = null;
虚引用:一个对象是否有虚引用的存在,完全不会对其生存时间构成影响,也无法通过虚引用取得一个对象。为一个对象设置虚引用关联的唯一目的就是能在这个对象被回收时收到一个系统通知
4.常用的垃圾收集算法有哪些?
一共有四种
标记-清除算法,会将存活的对象进行标记,然后清理掉未被标记的对象。它的缺点就是会产生大量不连续的内存碎片,导致无法给大对象分配内存。
标记 - 整理算法在虚拟机执行垃圾回收的过程中,先采用标记算法确定可回收对象,然后整理剩余的对象,将可用的对象移动到一起,使内存更加紧凑,连续的空间就更多。优点是不会有内存碎片。缺点是速度慢
复制算法会将内存分为等大小的两个区域,FROM和TO(TO中为空)。将被GC Root引用的对象从FROM放入TO中,再回收不被GC Root引用的对象。然后交换FROM和TO。优点是不会有内存碎片,主要不足是只使用了内存的一半
分代收集算法根据对象存活周期将内存划分为几块,不同块采用适当的收集算法。
一般将堆分为新生代和老年代,老年代的特点是每次垃圾收集时只有少量对象需要被回收,而新生代的特点是每次垃圾回收时都有大量的对象需要被回收,那么就可以根据不同代的特点采取最适合的收集算法
- 新生代使用:复制算法
- 老年代使用:标记 - 清除 或者 标记 - 整理 算法
5.什么情况下会触发Full GC?
-
调用 System.gc()
只是建议虚拟机执行 Full GC,但是虚拟机不一定真正去执行。不建议使用这种方式,而是让虚拟机管理内存
-
老年代空间不足
老年代空间不足的常见场景为前文所讲的大对象直接进入老年代、长期存活的对象进入老年代等
面试官追问:这种情况如何尽量避免?
-
应当尽量不要创建过大的对象以及数组。
-
可以通过 -Xmn 虚拟机参数调大新生代的大小,让对象尽量在新生代被回收掉,不进入老年代。
-
还可以通过 -XX:MaxTenuringThreshold 调大对象进入老年代的年龄,让对象在新生代多存活一段时间
-
-
空间分配担保失败
使用复制算法的 Minor GC 需要老年代的内存空间作担保,如果担保失败会执行一次 Full GC
-
JDK 1.7 及以前的永久代空间不足
在 JDK 1.7 及以前,HotSpot 虚拟机中的方法区是用永久代实现的
当系统中要加载的类、反射的类和调用的方法较多时,永久代可能会被占满,此时也会触发Full GC面试官打断问:这种情况怎么避免?
为避免以上原因引起的 Full GC,可采用的方法为增大永久代空间或转为使用 CMS GC
-
Concurrent Mode Failure
执行 CMS GC 的过程中同时有对象要放入老年代,而此时老年代空间不足(可能是 GC 过程中浮动垃圾过多导致暂时性的空间不足),便会报 Concurrent Mode Failure 错误,并触发 Full GC
面试官追问:说一下浮动垃圾是什么?
浮动垃圾是指并发清除阶段由于用户线程继续运行而产生的垃圾,这部分垃圾只能到下一次 GC 时才能进行回收(由于浮动垃圾的存在,因此需要预留出一部分内存,意味着 CMS 收集不能像其它收集器那样等待老年代快满的时候再回收。如果预留的内存不够存放浮动垃圾,就会出现 Concurrent Mode Failure)
6.说一下尽量避免内存泄漏的方法?
- 尽量不要使用
static
成员变量,减少生命周期; - 及时关闭无用的资源
- 不用的对象,可以手动设置为 null
7.谈谈你对类加载机制的了解?
虚拟机把描述类的数据从Class 文件加载到内存,并对数据进行校验、转换解析和初始化,最终形成可以被虚拟机直接使用的Java类型,这就是虚拟机的类加载机制
其中类加载的过程包括了加载
、验证
、准备
、解析
、初始化
五个阶段。其中验证
、准备
、解析
3 个部分统称为连接
面试官又问:这几个阶段发生的顺序一定是确定的吗?
加载
、验证
、准备
和初始化
这四个阶段发生的顺序是确定的,而解析
阶段则不一定,它在某些情况下可以在初始化
阶段之后开始,这是为了支持Java语言的运行时绑定(也称为动态绑定或晚期绑定)
8.类加载各阶段的作用分别是什么?
- 加载:查找并加载类的二进制数据
- 验证:确保被加载的类的正确性
- 准备:为类的静态变量分配内存,并将其初始化为默认值
- 解析:把类中的符号引用转换为直接引用
- 初始化:为类的静态变量赋予正确的初始值,JVM负责对类进行初始化,主要对类变量进行初始化
9.谈谈你对 CMS 垃圾收集器的理解?
CMS是Concurrent Mark-Sweep
的简称,是以牺牲吞吐量为代价来获得最短回收停顿时间的垃圾回收器。是使用标记-清除算法实现的
整个过程分为四步:
- 初始标记: 仅仅只是标记一下
GC Roots
能直接关联到的对象,速度很快,需要停顿,即存在STW(Stop The World) - 并发标记:进行
GC Roots Tracing
的过程,找出存活对象且用户线程此时可并发执行。它在整个回收过程中耗时最长,不需要停顿 - 重新标记:为了修正并发标记期间因用户程序继续运作而导致标记产生变动的那一部分对象的标记记录,需要停顿
- 并发清除:对标记的对象进行清除回收,不需要停顿
它的主要优点有:并发收集、低停顿
主要缺点有:吞吐量低,导致 CPU 利用率不够高、无法处理浮动垃圾、它使用的回收算法“标记-清除”算法会导致收集结束时会有大量空间碎片产生
10.JVM中为什么要把堆和栈区分出来呢?栈中不是也可以存储数据吗?
可以从这几个方面考虑:
- 从软件设计的角度看,栈代表了处理逻辑,而堆代表了数据。这样分开,使得处理逻辑更为清晰。分而治之的思想
- 堆与栈的分离,使得堆中的内容可以被多个栈共享(也可以理解为多个线程访问同一个对象)。这种共享的收益是很多的。一方面这种共享提供了一种有效的数据交互方式(如︰共享内存),另一方面,堆中的共享常量和缓存可以被所有栈访问,节省了空间
- 栈因为运行时的需要,比如保存系统运行的上下文,需要进行地址段的划分。由于栈只能向上增长,因此就会限制住栈存储内容的能力。而堆不同,堆中的对象是可以根据需要动态增长的,因此栈和堆的拆分,使得动态增长成为可能,相应栈中只需记录堆中的一个地址即可
最后喜欢的小伙伴,记得三连哦!😏🍭😘
以上是关于面试题JVM篇-10道常见面试题的主要内容,如果未能解决你的问题,请参考以下文章