前言:
原文地址:https://blog.csdn.net/weixin_42599693/article/details/107514619
本篇文章整理了一些网上关于jvm比较火热的面试题,答案有作者自己写的也有从网上借鉴的,当然借鉴的都已标注原文地址,有不合适的地方留言给我,或者有比较代表性的面试题也留言给我。
面试题:
1.说一下 jvm 的主要组成部分?及其作用?
类加载器:负责加载.class文件到内存中。Class loader检验是否符合文件规范,符合则加载到内存中,加载过程中常量,类信息,接口信息,方法信息会被存储到对应的运行时数据区(方法区,堆)
执行引擎(解释器):负责解释加载到内存的字节码转化为操作系统认识的底层命令,对接操作系统,让操作系统执行对应的命令。
本地接口库:本地接口库应该是为了java能够调用其他语言所实现的功能,java调用C或者C++。
运行时数据区:jvm内存区域,程序加载到内存的中信息存储位置以及运行时信息存储位置。
简单的说一下程序执行过程,加载器 -> 执行器:寻找main函数运行入口。每启动一个java程序相应的启动一个jvm进程,进程与进程之间是相互隔离的。
2.说一下 jvm 运行时数据区?
共有5部分构成:程序计数器,本地方法栈,虚拟机栈,方法区,堆。
其中堆,方法区是线程共享部分;程序计数器,本地方法栈,虚拟机栈是线程隔离部分。
1.程序计数器
程序计数器是很小的一块内存区域,用来存储当前线程所执行字节码的顺序指令。简单的解释一下:java文件被编译器编译为.class的字节码文件,类加载器加载到内存中。当一个线程需要执行某个方法时需要那类中方法的字节码进行执行,实际上字节码是一些底层的指令,第一步干什么,第二步干什么,当前线程执行到哪一步都会存储到当前线程的私有空间:计数器中。
2.虚拟机栈(答案引用:http://blog.csdn.net/wangwenjun69)
虚拟机栈也是线程私有的,每一个方法被执行的时候都会创建一个栈帧,存放在虚拟机栈中,虚拟机栈的结构大致如下所示
每一个方法被调用直到完成的过程,就对应着一个栈帧在虚拟机栈中从如栈到出栈的过程,其中局部变量表就是很多人所说的栈(堆栈地址),他所存放的是基本类型数据和对象的引用类型(reference),其局部变量表中的数据在编译时期就基本上已经确认了,操作栈主要就是压栈或者弹栈,其中动态链接这一部分我个人的理解是动态寻找获取下一个方法的入口地址信息等(个人理解的,有可能不准确)大多数JVM的虚拟机栈都可以动态扩展的,当无法申请到足够的内存时候会抛出OutOfMemoryError。
3.本地方法栈
本地方法栈和虚拟机栈一样的结构,只是他只支持被navite修饰的方法
4.方法区
方法区和java堆一样,是所有线程共享的内存区域。它存储了每一个类的结构信息,例如运行时常量池,字段和方法数据,构造函数和普通方法的字节码内容,还包括一些在类,实例,接口初始化时用到的特殊方法。
5.堆
堆在java内存单元中占据着比较大的比重,也是最大的一部分内存单元,在虚拟机启动的时候,该部分的内存就会被创建,所有的对象创建,以及数组内存的申请分配都是在该内存单元上发生的。由于堆内存所占的比重比较大,因此他也就是java垃圾回收器最关注的一块内存,因此该内存单元也被称为GC堆。
如果以后您了解了GC机制,您会知道,Java允许内存单元不连续,只要逻辑上是连续的即可,这部分的内存也是可以进行扩展的,在启动虚拟机时我们可以通过-Xmx,-Xms进行控制,当堆中的内存再也申请不到的时候就会抛出内存溢出的异常,另外该内存空间是线程共享的,我们经常使用到的锁其实就是在这部分内存中活动。
补充一下,我们常说看一下虚拟机的堆栈日志,其实就是jvm运行时日志,该日志包含jvm中每一个线程的执行过程,从方法区拿什么数据,从栈中获取那个引用,在堆上创建了那个对象等。
3.说一下堆栈的区别?
1.堆是线程共享区域,栈是线程独享区域
2.堆存放对象实体以及数组信息,栈存放基本类型数据和对象数组引用地址
注意一点:递归很容易导致栈溢出,递归会不断的调用方法,每执行一个方法都会形成一个栈帧,直到超过栈最大存储量。
4.队列和栈是什么?有什么区别?
队列和栈都是线性表的数据结构。
队列是先进先出,队尾进队头出,栈是先进后出,只有一个口能操作队列,压栈和弹栈。
5.什么是双亲委派模型?
这是面试官考察我们对jvm类加载知识点的掌握:类加载器有哪些,双亲委派模型,为什么这么设计
1.类加载器分为三类依次从上往下
Bootstrap classLoader:主要负责加载核心的类库(java.lang.*等),构造ExtClassLoader和APPClassLoader。
ExtClassLoader:主要负责加载jre/lib/ext目录下的一些扩展的jar。
AppClassLoader:主要负责加载应用程序的主函数类
2.双亲委派,当加载一个class字节码文件从最底层的AppClassLoader一层一层向上询问是否加载过此类,类加载优先级是 Bootstrap -> ExtClassLoader -> AppClassLoader。当父类不能加载此class才会将加载的权利依次向下放。
3.安全性考虑,如果有人想要替换系统级别的类比如:Object,String等,在这种机制下是不能加载到内存中,因为系统级别的类已经被最上级的加载器加载成功了。
6.说一下类加载的执行过程?
类加载概括性说:将.class文件加载到内存中。记载过程中分为三个步骤:加载,链接,初始化。
加载:
将class文件字节码从各种来源加载到内存中。两个知识点:来源,加载。
字节码的来源有本地class文件,三方class文件,网络获取。
加载器分为启动类加载器,扩展类加载器,应用类加载器,用户自定义加载器。三方class文件以及网络字节码需要通过用户自定义加载器进行加载。
举例:很多人会把自己编译后的class文件加密,在加载时就需要通过自定义加载器进行加载。
链接:
分为验证,准备,解析。
验证:校验加载到内存中字节码是否符合jvm规范。
准备:准备阶段则负责为类的静态属性分配内存,并设置默认初始值;
解析:将常量池内的符号引用替换为直接引用的过程。
初始化:
1.类的初始化阶段主要是对类变量进行初始化,在Java类中对类变量指定初始值有两种方式:
声明类变量时指定初始值(static修饰的变量)
使用静态初始化块为类变量指定初始值(static修饰的代码块)
2.JVM初始化一个类一般包括如下几个步骤:
假如这个类还没有被加载和连接,程序先加载并连接该类;
假如该类的直接父类还没有被初始化,则先初始化其直接父类;
假如类中有初始化语句,则系统依次执行这些初始化语句
当执行第二步时,系统对直接父类的初始化也遵循此1、2、3步骤,如果该直接父类又有直接父类,系统再次重复这三步,所以JVM最先初始化的总是java.lang.Object类。
7.怎么判断对象是否可以被回收?
需要回答出:根搜索算法。
主要是通过Roots对象作为起点进行搜索,搜索走过的路径称为“引用链”,当一个对象到 Roots 没有任何的引用链相连时时,证明此对象不可用,当然被判定为不可达的对象不一定就会成为可回收对象。被判定为不可达的对象要成为可回收对象必须至少经历两次标记过程,如果在这两次标记过程中仍然没有逃脱成为可回收对象的可能性,则基本上就真的成为可回收对象了,能否被回收其实主要还是要看finalize()方法有没有与引用链上的对象关联,如果在finalize()方法中有关联则自救成功,改对象不可被回收,反之如果没有关联则成功被二次标记成功,就可以称为要被回收的垃圾了。
Roots对象有哪些?
1、引用栈帧中的本地变量表的所有对象;
2、引用方法区中静态属性的所有对象;
3、引用方法区中常量的所有对象;
4、引用Native方法的所有对象。
8.java 中都有哪些引用类型?
Jdk1.2之后有四种引用类型:强引用,软引用,弱引用,虚拟引用。
强引用:A a = new A();a的引用就是强引用,只要有强引用存在,GC就不会回收此对象,如果内存不足则抛出OutOfMemoryError。如果想要快速回收对象A,则将引用a = null ,强行中断引用与对象之间的联系。
软引用:软引用是用来描述一些非必需但仍有用的对象,在内存足够的时候,软引用对象不会被回收,只有在内存不足时,系统则会回收软引用对象,如果回收了软引用对象之后仍然没有足够的内存,才会抛出内存溢出异常。这种特性常常被用来实现缓存技术,比如网页缓存,图片缓存等。在 JDK1.2 之后,用java.lang.ref.SoftReference类来表示软引用
弱引用:弱引用的引用强度比软引用要更弱一些无论内存是否足够,只要 JVM 开始进行垃圾回收,那些被弱引用关联的对象都会被回收。在 JDK1.2 之后,用 java.lang.ref.WeakReference 来表示弱引用。
虚拟引用:虚引用是最弱的一种引用关系,如果一个对象仅持有虚引用,那么它就和没有任何引用一样,它随时可能会被回收,在 JDK1.2 之后,用 PhantomReference 类来表示,通过查看这个类的源码,发现它只有一个构造函数和一个 get() 方法,而且它的 get() 方法仅仅是返回一个null,也就是说将永远无法通过虚引用来获取对象,虚引用必须要和 ReferenceQueue 引用队列一起使用。
9.说一下 jvm 有哪些垃圾回收算法?
常用的垃圾回收算法有如下四种:标记-清除、复制、标记-整理和分代收集。
标记-清除算法
从算法的名称上可以看出,这个算法分为两部分,标记和清除。首先标记出所有需要被回收的对象,然后在标记完成后统一回收掉所有被标记的对象。这个算法简单,但是有两个缺点:一是标记和清除的效率不是很高;二是标记和清除后会产生很多的内存碎片,导致可用的内存空间不连续,当分配大对象的时候,没有足够的空间时不得不提前触发一次垃圾回收。
复制算法
这个算法将可用的内存空间分为大小相等的两块,每次只是用其中的一块,当这一块被用完的时候,就将还存活的对象复制到另一块中,然后把原已使用过的那一块内存空间一次回收掉。这个算法常用于新生代的垃圾回收。复制算法解决了标记-清除算法的效率问题,以空间换时间,但是当存活对象非常多的时候,复制操作效率将会变低,而且每次只能使用一半的内存空间,利用率不高。
标记-整理算法
这个算法分为三部分:一是标记出所有需要被回收的对象;二是把所有存活的对象都向一端移动;三是把所有存活对象边界以外的内存空间都回收掉。标记-整理算法解决了复制算法多复制效率低、空间利用率低的问题,同时也解决了内存碎片的问题。
分代收集算法
根据对象生存周期的不同将内存空间划分为不同的块,然后对不同的块使用不同的回收算法。一般把Java堆分为新生代和老年代,新生代中对象的存活周期短,只有少量存活的对象,所以可以使用复制算法,而老年代中对象存活时间长,而且对象比较多,所以可以采用标记-清除和标记-整理算法。
10.说一下 jvm 有哪些垃圾回收器?
新生代收集器:
serial收集器
ParNew收集器--是Serial收集器的多线程版本
Parallel Scaverge收集器
老年代收集器:
Serial Old收集器--是Serial收集器的老年代版本
Parallel Old--是Parallel Scavenge收集器的老年代版本
CMS收集器
新生代+老年代收集器
G1 收集器
ZGC 收集器
11.详细介绍一下 CMS 垃圾回收器?
(答案引用:https://www.cnblogs.com/chenpt/p/9803298.html)
一种以获取最短回收停顿时间为目标的收集器。
特点:基于标记-清除算法实现。并发收集、低停顿。
应用场景:适用于注重服务的响应速度,希望系统停顿时间最短,给用户带来更好的体验等场景下。如web程序、b/s服务。
CMS收集器的运行过程分为下列4步:
初始标记:标记GC Roots能直接到的对象。速度很快但是仍存在Stop The World问题。
并发标记:进行GC Roots Tracing 的过程,找出存活对象且用户线程可并发执行。
重新标记:为了修正并发标记期间因用户程序继续运行而导致标记产生变动的那一部分对象的标记记录。仍然存在Stop The World问题。
并发清除:对标记的对象进行清除回收。
CMS收集器的内存回收过程是与用户线程一起并发执行的。
CMS收集器的工作过程图:
CMS收集器的缺点:
对CPU资源非常敏感。
无法处理浮动垃圾,可能出现Concurrent Model Failure失败而导致另一次Full GC的产生。
因为采用标记-清除算法所以会存在空间碎片的问题,导致大对象无法分配空间,不得不提前触发一次Full GC。
12.新生代垃圾回收器和老生代垃圾回收器都有哪些?有什么区别?
新生代收集器:serial收集器,ParNew收集器,Parallel Scaverge收集器
老年代收集器:Serial Old收集器,Parallel Old,CMS收集器
新生代内存占用较少,一般采用复制算法回收器进行回收,以空间换时间。
老年代内存占用较多,一般采用标记-清除或者标记-整理算法回收器进行回收,因为内存占用空间比较大所以不能采用复制算法。
13.简述分代垃圾回收器是怎么工作的?
同学们自己收集下答案
14.说一下 jvm 调优的工具?
Java的jconsole,visualVM。阿里的Arthas
15.常用的 jvm 调优的参数都有哪些
(答案引用:https://www.cnblogs.com/wuhg/p/9707974.html)
-Xms:初始堆大小
-Xms:最大堆大小
-XX:NewSize=n:设置年轻代大小
-XX:NewRatio=n:设置年轻代和年老代的比值。如:为3表示年轻代和年老代比值为1:3,年轻代占整体的1/4
-XX:SurvivorRatio=n:年轻代中Eden区与两个Survivor区的比值。注意Survivor区有两个。如3表示Eden: 3 Survivor:2,一个Survivor区占整个年轻代的1/5
-XX:MaxPermSize=n:设置持久代大小
说明:
1、一般初始堆和最大堆设置一样,因为:现在内存不是什么稀缺的资源,但是如果不一样,从初始堆到最大堆的过程会有一定的性能开销,所以一般设置为初始堆和最大堆一样。64位系统理论上可以设置为无限大,但是一般设置为4G,因为如果再大,JVM进行垃圾回收出现的暂停时间会比较长,这样全GC过长,影响JVM对外提供服务,所以不能太大。一般设置为4G。
2、-XX:NewRaio和-XX:SurvivorRatio这两个参数,都是设置年轻代和年老代的大小的,设置一个即可,第一是设置年轻代的大小,第二个是设置比值,理论上设置一个既可以满足需求