JVM面试题

1,048 阅读10分钟

本文是在网上看了各种面试指南收集的题目及答案。无意冒犯各位原创作者,如果在您的博客或者写作平台有相似问题答案可以跟我说,我给您链接加上,我只是为了方便以后自己需要的时候刷一刷,不用在到处找题。

JVM垃圾处理方法(标记清除、复制、标记整理)

• 标记-清除算法

○ 标记阶段:先通过根节点,标记所有从根节点开始的对象,未被标记的为垃圾对象
○ 清除阶段:清除所有未被标记的对象

• 复制算法

○ 将原有的内存空间分为两块,每次只使用其中一块,在垃圾回收时,将正在使用的内存中的存活对象复制到未使用的内 存块中,然后清除正在使用的内存块中的所有对象。

• 标记-整理

○ 标记阶段:先通过根节点,标记所有从根节点开始的可达对象,为被标记的为垃圾对象
○ 整理阶段:将所有的存活对象压缩到内存的一段,之后清理边界所有的空间

• 三种算法的比较

○ 效率:复制算法 > 标记/整理算法 > 标记/清除算法(此处的效率只是简单的对比时间复杂度,实际情况不一定如 此)。
○ 内存整齐度:复制算法=标记/整理算法>标记/清除算法。
○ 内存利用率:标记/整理算法=标记/清除算法>复制算法。

JVM如何GC,新生代,老年代,持久代,都存储哪些东西,以及各个区的作用?

• 新生代

○ 在方法中去new一个对象,那这方法调用完毕后,对象就会被回收,这就是一个典型的新生代对象。

• 老年代

○ 在新生代中经历了N次垃圾回收后仍然存活的对象就会被放到老年代中。而且大对象直接进入老年代
○ 当Survivor空间不够用时,需要依赖于老年代进行分配担保,所以大对象直接进入老年代

• 永久代

○ 即方法区。

GC用的引用可达性分析算法中,哪些对象可作为GC Roots对象?

• Java虚拟机栈中的对象
• 方法区中的静态成员
• 方法区中的常量引用对象
• 本地方法区中的JNI(Native方法)引用对象。

什么时候进行MinGC,FullGC

• MinGC

○ 新生代中的垃圾收集动作,采用的是复制算法
○ 对于较大的对象,在Minor GC的时候可以直接进入老年代

• FullGC

○ Full GC是发生在老年代的垃圾收集动作,采用的是标记-清除/整理算法。
○ 由于老年代的对象几乎都是在Survivor区熬过来的,不会那么容易死掉。因此Full GC发生的次数不会有Minor GC那么 频繁,并且Time(Full GC)>Time(Minor GC)

各个垃圾收集器是怎么工作的

• Serial收集器

○ 是一个单线程的收集器,不是只能使用一个CPU。在进行垃圾收集时,必须暂停其他所有的工作线程,直到收集结束。
○ 新生代采用复制算法,Stop-The-World
○ 老年代采用标记-整理算法,Stop-The-World
○ 简单高效,Client模式下默认的新生代收集器

• ParNew收集器

○ ParNew收集器是Serial收集器的多线程版本
○ 新生代采用复制算法,Stop-The-World
○ 老年代采用标记-整理算法,Stop-The-World
○ 它是运行在Server模式下首选新生代收集器
○ 除了Serial收集器之外,只有它能和CMS收集器配合工作

• ParNew Scanvenge收集器

○ 类似ParNew,但更加关注吞吐量。目标是:达到一个可控制吞吐量的收集器。
○ 停顿时间和吞吐量不可能同时调优。我们一方面希望停顿时间少,另外一方面希望吞吐量高,其实这是矛盾的。因为:在GC的时候,垃圾回收的工作总量是不变的,如果将停顿时间减少,那频率就会提高;既然频率提高了,说明就会频繁的进行GC,那吞吐量就会减少,性能就会降低。

• G1收集器

○ 是当今收集器发展的最前言成果之一,对垃圾回收进行了划分优先级的操作,这种有优先级的区域回收方式保证了它的高效率
○ 最大的优点是结合了空间整合,不会产生大量的碎片,也降低了进行gc的频率
○ 让使用者明确指定指定停顿时间

• CMS收集器:(Concurrent Mark Sweep:并发标记清除老年代收集器)

○ 一种以获得最短回收停顿时间为目标的收集器,适用于互联网站或者B/S系统的服务器上
○ 初始标记(Stop-The-World):根可以直接关联到的对象
○ 并发标记(和用户线程一起):主要标记过程,标记全部对象
○ 重新标记(Stop-The-World):由于并发标记时,用户线程依然运行,因此在正式清理前,再做修正
○ 并发清除(和用户线程一起):基于标记结果,直接清理对象
○ 并发收集,低停顿

Java虚拟机内存的划分,每个区域的功能

• 程序计数器(线程私有)

○ 线程创建时创建,执行本地方法时其值为undefined。

• 虚拟机栈(线程私有)

○ (栈内存)为虚拟机执行java方法服务:方法被调用时创建栈帧-->局部变量表->局部变量、对象引用
○ 如果线程请求的栈深度超出了虚拟机所允许的深度,就会出现StackOverFlowError。-Xss规定了栈的最大空间
○ 虚拟机栈可以动态扩展,如果扩展到无法申请到足够的内存,会出现OOM

• 本地方法栈(线程私有)

○ java虚拟机栈是为虚拟机执行java方法服务的,而本地方法栈则为虚拟机执使用到的Native方法服务。
○ Java虚拟机没有对本地方法栈的使用和数据结构做强制规定。Sun HotSpot把Java虚拟机栈和本地方法栈合二为一
○ 会抛出StackOverFlowError和OutOfMemoryError

• Java堆

○ 被所有线程共享,在Java虚拟机启动时创建,几乎所有的对象实例都存放到堆中
○ GC的管理的主要区域
○ 物理不连续,逻辑上连续,并可以动态扩展,无法扩展时抛出OutOfMemoryError

• 方法区

○ 用于存储已被虚拟机加载的类信息、常量、静态变量、即使编译器编译后的代码的数据
○ Sun HotSpot 虚拟机把方法区叫做永久待(Permanent Generation) • 运行时常量池
○ 受到方法区的限制,抛出OutOfMemoryError

用什么工具可以查出内存泄漏

• MemoryAnalyzer:一个功能丰富的JAVA堆转储文件分析工具,可以帮助你发现内存漏洞和减少内存消耗
• EclipseMAT:是一款开源的JAVA内存分析软件,查找内存泄漏,能容易找到大块内存并验证谁在一直占用它,它是基于Eclipse RCP(Rich Client Platform),可以下载RCP的独立版本或者Eclipse的插件
• JProbe:分析Java的内存泄漏。

JVM如何加载一个类的过程,双亲委派模型中有哪些方法有没有可能父类加载器和子类加载器,加载同一个类?如果加 载同一个类,该使用哪一个类?

• 双亲委派概念

○ 如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器去完成,每一个层次的加载器都是如此,因此所有的类加载请求都会传给顶层的启动类加载器,只有当父加载器反馈自己无法完成该加载请求(该加载器的搜索范围中没有找到对应的类)时,子加载器才会尝试 自己去加载。

• 加载器

○ 启动(Bootstrap)类加载器:是用本地代码实现的类装入器,它负责将 <Java_Runtime_Home>/lib下面的类库加载到内存中(比如rt.jar)。由于引导类加载器涉及到虚拟机本地实现细节,开发者无法直接获取到启动类加载器的引用,所以不允许直接通过引用进行操作。
○ 标准扩展(Extension)类加载器:是由Sun的ExtClassLoader(sun.misc.LauncherExtClassLoader)实现的。它负责将<Java_Runtime_Home>/lib/ext或者由系统变量 java.ext.dir指定位置中的类库加载到内存中。开发者可以直接使用标准扩展类加载器。   
○ 系统(System)类加载器:由Sun的AppClassLoader(sun.misc.LauncherAppClassLoader)实现的。它负责将系统类路径(CLASSPATH)中指定的类库加载到内存中。开发者可以直接使用系统类加载器。除 了以上列举的三种类加载器,还有一种比较特殊的类型 — 线程上下文类加载器。

• 如果加载同一个类,该使用哪一个类?

○ 父类的

JVM线程死锁,你该如何判断是因为什么?如果用VisualVM,dump线程信息出来,会有哪些信息

常常需要在隔两分钟后再次收集一次thread dump,如果得到的输出相同,仍然是大量thread都在等待给同一个 地址上锁,那么肯定是死锁了

什么是类的加载

类的加载指的是将类的.class文件中的二进制数据读入到内存中,将其放在运行时数据区的方法区内,然后在堆区创建一个java.lang.Class对象,用来封装类在方法区内的数据结构。类的加载的最终产品是位于堆区中的Class对象,Class对象封装了类在方法区内的数据结构,并且向Java程序员提供了访问方法区内的数据结构的接口。

类的生命周期

•加载,查找并加载类的二进制数据,在Java堆中也创建一个java.lang.Class类的对象
•连接,连接又包含三块内容:验证、准备、初始化。1)验证,文件格式、元数据、字节码、符号引用验证;2)准备,为类的静态变量分配内存,并将其初始化为默认值;3)解析,把类中的符号引用转换为直接引用
•初始化,为类的静态变量赋予正确的初始值
•使用,new出对象程序中使用
•卸载,执行垃圾回收

判断对象是否存活一般有两种方式:

•引用计数:每个对象有一个引用计数属性,新增一个引用时计数加1,引用释放时计数减1,计数为0时可以回收。此方法简单,无法解决对象相互循环引用的问题。
•可达性分析(ReachabilityAnalysis):从GCRoots开始向下搜索,搜索所走过的路径称为引用链。当一个对象到GCRoots没有任何引用链相连时,则证明此对象是不可用的,不可达对象。

本文部分问题答案来源下面链接:

juejin.cn/post/684490…