JVM面试题

98 阅读16分钟

JVM面试题

1、什么是JVM、JRE和JDK?
(1)JVM是java虚拟机,是执行字节码文件的
(2)JRE是JVM和java核心类库,是执行java文件的基础环境
(3)JDK除了包含JVM、java核心类库,还包含第三方库,是开发环境

2、JVM的内存模型?
JVM包括堆区、方法区、栈区、本地方法栈区、程序计数器。其中堆区和方法区是线程共享的,栈区、本地方法栈和程序计数器是线程私有的。堆区用来存储对象信息;方法区用来存储类信息,静态变量,常量,编译器编译的代码等;栈区用来存储局部变量,方法出口,操作数栈和动态链接等;本地方法栈区用来调用C的方法;程序计数器是用来记录线程执行的下一条指令位置。

3、有哪些类加载器?
(1)bootstrap:根类加载器,用来加载java核心类
(2)extension:扩展类加载器,用来加载第三方类
(3)app:app类加载器,用来加载程序员写的类
(4)自定义类加载器

4、什么是双亲委派原则?
当加载一个类时,首先由app类加载器判断此类是否被加载过,如果被加载过则不再重复加载,如果没有,让extension类加载器判断是否被加载过,以此类推,如果根加载器判断没有加载过,那么根加载器会尝试加载。如果此类不适合根加载器加载,根加载器会委派给extension加载器,以此类推。

5、类加载会经历哪些阶段?
(1)加载阶段:将class文件加载到内存
(2)校验阶段:判断是否满足字节码格式
(3)准备阶段:将静态变量赋默认值
(4)解析阶段:将符号引用改成实际地址引用
(5)初始化阶段:静态变量赋初始值,并且执行静态方法

6、JVM如何判断对象是否存活?
可达性分析算法。有一系列的GC ROOTS的根对象作为起点,寻找他们的引用,如果找不到,说明此对象没有被引用,可以被回收。

7、GC ROOTS有哪些?
可以是栈区的局部变量,堆区中的对象,方法区中的静态变量引用,常量池中的引用,本地方法区对象等。

8、对象不可达,会立即被回收吗?
不会。GC时会被标记,然后进行判断,判断是否有finalize方法,如果有finalize方法,并且没有被执行过,那么会将对象放入队列中。稍后,JVM会让finalizer线程去执行finalize方法。

9、垃圾回收算法有哪些?
(1)标记-清理算法:首先标记要回收的对象,然后清理他们。缺点是久而久之,会有很多空间碎片
(2)标记-复制算法:只使用一半的空间存储对象,首先标记要回收的对象,然后将存活对象复制到另一半空间,在将原来一半的空间都回收。缺点是浪费了一半的空间
(3)标记-整理算法:首先标记要回收的对象,然后将不回收对象移动到一起,最后回收标记的对象

10、JVM堆内存结构?
JVM堆内存分两块区域,新生代和老年代。新生代中有分eden区和两个servivor区,他们的内存大小比例是8:1:1。

11、新创建的对象在堆内存中怎么存放?
新创建的对象会放到eden区,如果eden区放不下,会触发minor gc,eden区和servivor1区的对象会复制到servivor2区。如果servivor2区放不下,触发full gc。

11、对象什么时候会进入老年代?
(1)大对象可以通过参数设置,直接进入老年代,避免在新生代来回复制
(2)在新生代的对象,每次minor gc后都会年龄+1,如果年龄超过阈值,移动到老年代
(3)根据动态年龄判定规则,如果Survivor区年龄1+年龄2+年龄3+年龄n的对象总和大于Survivor区的50%,此时年龄n以上的对象会进入老年代,不一定要达到15岁。

12、方法区的对象如何回收?
方法区的对象主要回收的是常量和无用的类。常量判断是否还有引用。无用的类满足三点:第一点,是否有这个类的对象在堆区;第二点,这个类的类加载器是否被回收;第三点,这个类的类对象是否被引用。

13、空间分配担保原则?
在执行minor gc之前,JVM会判断老年代连续内存空间的大小是否大于新生代所有对象的大小总和,如果不大于,检查是否允许担保失败。如果允许,再检查老年代连续内存空间的大小是否大于新生代所有对象的大小的平均值,如果大于,进行minor gc。如果小于,进行full gc。

14、触发full gc的情况?
(1)对象在新生代放不下
(2)手动触发full gc,调用system.gc()方法
(3)不满足空间分配担保原则的情况下

15、介绍几个常见的垃圾回收器?
(1)parallel scavenge:此垃圾回收器作用于新生代,基于标记-复制算法。是重视吞吐量的垃圾回收器。所谓吞吐量是(use code time)/(use code time+gc time)。有两个参数可以调整吞吐量:一个是最大GC时间,另一个是直接设置吞吐量。
(2)parallel old:此垃圾回收器作用于老年代,基于标记-整理算法。与parallel scavenge配合使用可以提高吞吐量。
(3)g1:此垃圾回收器作用于整个堆区。此垃圾回收器没有把堆区物理划分成新生代和老年代,而是有一个个region块。每个region块可以是egen、servivor和old。基于标记-整理算法进行回收。当eden区满了会触发minorGC;当堆中对象超过内存的45%(默认值可调),触发mixedGC;当对象存不下了,触发fullGC。
(4)CMS:CMS是第一个工作线程与垃圾回收线程同时工作的垃圾回收器。其垃圾回收阶段有四个,分别是:初始标记阶段,触发STW,这个阶段只标记GC ROOTS;并发标记阶段,这个阶段会根据GC ROOTS标记有效对象和无效对象;重新标记阶段,触发STW,这个阶段会重新标记有效对象和无效对象,并且将三色标记算法漏标的对象标记出来;并发清理阶段,回收垃圾,但是这个阶段也会产生浮动垃圾。

16、常见调优参数?
(1)-Xms:堆内存最小值;-Xmx:堆内存最大值
(2)-Xmn:堆内存中年轻代大小
(3)-Xss:线程栈大小
(4)-XX:NewSize:年轻代最小值;-XX:MaxNewSize:年轻代最大值
(5)-XX:PermSize:永久代最小值;-XX:MaxPermSize:永久代最大值
(6)-XX:+UseG1:使用G1垃圾回收器
(7)-XX:MetaspaceSize:元空间最小值;-XX:MaxMetaspaceSize:元空间最大值
(8)-XX:+HeapDumpOnOutOfMemoryError:发生OutOfMemoryError时,将堆栈信息存入文件中
(9)-XX:PrintGCDetails:打印详细的GC日志

17、G1中的CSet和RSet重要概念?
(1)CSet中存放着老年代中要被回收的Region
(2)RSet是每个Region都有的,里面存放着其他Region对当前Region对象的引用信息

18、三色标记算法?
对象在逻辑上分为黑色、灰色和白色。黑色表示对象自身被标记,并且其引用的对象也被标记;灰色表示自身被标记,但是其引用的对象没有被标记;白色表示自身和其引用的对象都没有被标记。
标记过程:
(1)初始时所有对象都被设置为白色。
(2)从GC ROOTS开始,将它们标记为灰色,并加入一个工作列表。
(3)遍历每个灰色对象的子对象,并将它们标记为灰色,同时将它们添加到工作列表中。
(4)对于每个灰色对象,如果其所有子对象都已经处理完毕,则将其标记为黑色,并且移出工作列表。
(5)重复步骤3和4,直到工作列表为空为止。
当所有对象都被处理完毕后,白色的对象就被视为不可达,并可以进行垃圾收集。

19、CMS和G1出现的漏标问题和解决?
漏标现象发生在并发标记过程,例如:某一灰色对象引用一个对象,但是这个对象还没有被标记,在并发标记时,这个引用消失了,但是某一黑色对象引用了这个对象,但是黑色对象的引用不会再扫描了,导致垃圾回收器会以为这个对象没有引用,被当成了垃圾。
CMS的解决方案是增量标记:当黑色对象新增引用的时候,将黑色对象变成灰色对象,重新标记时再次扫描其引用。
G1的解决方案是SATB:当灰色对象的引用消失时,将其存入堆栈中,下次还可以扫描到白色的对象,然后根据RSet判断这个对象是否有引用,没有就当做垃圾处理。

20、对象的创建过程?
(1)没有加载类的话,先加载类
(2)申请内存空间
(3)给成员变量赋默认值
(4)执行构造方法,给成员变量赋初始值

21、对象在堆内存中的布局?
(1)普通对象:对象头、指向类对象的指针、成员变量、对齐字节
(2)数组对象:对象头、指向类对象的指针、数组长度、数组数据、对齐字节

22、对象头中包含什么?
对象头信息在不同锁的状态下是不一样的:
(1)无锁:25位-hash值、4位-GC年龄、1位-是否偏向锁(0)、2位-锁状态(01)
(2)偏向锁:23位-线程ID、2位-空、4位-GC年龄、1位-是否偏向锁(1)、2位-锁状态(01)
(3)轻量锁:30位-锁信息、2位-锁状态(00)
(4)重量锁:30位-锁信息、2位-锁状态(10)
(5)GC:30位-空、2位-锁状态(11)
(6)当对象在无锁状态下,对象头中存储了hashcode值后,如果对其加锁,它是无法变成偏向锁的,因为偏向锁的对象头前25位要存储线程ID,但是位置已经被hashcode值占据了。

23、线程栈包含的信息?
(1)本地变量表:存放方法中局部变量值
(2)操作栈:存放操作使用的临时变量值
(3)动态链接:调用其他方法时,可以找到并且调用
(4)返回地址:方法执行结束时,返回的地址

24、JVM常见的调优命令?
(1)top命令查看哪个进程占用cpu和内存高
(2)[top -Hp 进程号] 这个命令查看进程中哪个线程占用的cpu和内存高
(3)[jstack 进程号 > dump.txt] 这个命令将进程中的所有线程的堆栈信息写入dump.txt文件中
(4)[jmap -histo 进程号|head -20] 这个命令查看进程创建出来的数量在前20的对象 
(5)[jmap -heap 进程号] 这个命令可以查看新生代和老年代的内存占用情况
(6)[jstat -gc 进程号 打印时间] 这个命令是每隔一段时间打印一次GC信息

25、JVM的TLAB是什么?
一般来说,生成对象需要向新生代申请内存空间,而堆又是全局共享的,新生代内存又是规整的,是通过一个指针来划分的。

image.png

内存是紧凑的,新对象分配时,指针向右移动对象大小即可,这叫指针加法。
如果多个线程在分配对象,那么这个指针就成为热点资源,需要互斥,那分配的效率就低了。
于是有了TLAB机制,每个线程在新生代有一个内存区域,这个区域只允许此线程分配对象,允许其他线程访问这块区域。

image.png

可以看到每个线程有自己的内存分配区域,短一点的箭头代表TLAB内部的分配指针。如果区域用完了会再次去申请。不过每次申请的大小不固定,会根据线程启动到现在的历史信息来调整。比如这个线程一直在分配内存那么TLAB就大些,否则就小些。
TLAB只能分配小对象,大对象还需要在共享的eden区申请空间。

26、什么是垃圾回收的“concurrent mode failure”?
这个错误是使用CMS垃圾回收器时可能发生的。CMS在进行垃圾回收时大致可分为4步,初始标记,并发标记,重新标记和并发清理。在并发清理过程中,业务线程与垃圾回收线程是共同工作的,这就有可能产生浮动垃圾,如果浮动垃圾特别多,占用空间大,而新生代中又有很多对象要移动到老年代,此时,老年代空闲空间不够的话,就会出现这个错误。在出现这个错误后,CMS会在老年代使用serial old机制进行回收垃圾,效率极低。

27、什么是解释执行和编译执行?
解释执行是一边读取代码,一边将代码解释成机器码去执行。
编译执行是将代码先编译成可执行代码,然后执行。
JVM是两者皆有,一般代码是解释执行,而热点代码是编译后执行。
JVM也可以通过参数设置执行的模式。

28、什么是Java中的直接内存? 直接内存是JVM与操作系统内核共享的一块内存区域。用于NIO的网络传输,提高传输效率。
理论上我们在java中想要操作堆外内存,需要将其中数据拷贝到堆内,因此有了直接内存,允许java直接访问,省去拷贝步骤,提高I/O效率。不过直接内存不受垃圾回收器管理,需要手动释放,防止内存泄漏。
在java中可以使用Unsafe类和NIO类库使用直接内存。在JDK1.8以后,所谓方法区中的元空间使用的就是直接内存

29、java的逃逸分析?
代码中有些对象只是局部变量,不会被方法外的地方引用,随着方法结束,这个对象就变成了垃圾。这种情况下,这个对象是否不需要在堆上分配,直接在栈上分配,随着方法结束,弹出栈,减轻GC的工作。分析这种情况就是逃逸分析。

30、什么是强引用、软引用、弱引用和虚引用?
强引用:我们平时用new关键字创建的对象就是强引用。只要对象有强引用,就不会被垃圾回收器回收
软引用:垃圾回收时,如果GC后空间依然不够用,会将软引用的对象进行回收
弱引用:垃圾回收时,不管GC后空间够不够用,都会回收弱引用的对象
虚引用:虚引用与弱引用一样在GC后一定被回收。而且通过虚引用无法get到对象

31、为什么要划分出新生代和老年代?
新生代中对象少,垃圾多,适合用复制算法进行清理;老年代对象多,垃圾少,适合用标记清理算法。划分出新生代和老年代也是为了提高垃圾回收效率。

32、为什么在JDK1.8将永久代移到堆外?
在JDK1.8之前,永久代在堆内,受到垃圾回收器管理。但是永久代中的对象基本不会被回收,所以每次垃圾回收的效率不高。而且,每次永久代满了,都会触发FullGC,而回收效率又很低,不划算。所以移到堆外去了,使用直接内存,成为元空间。

33、字符串常量存在哪里?
字符串常量是存在字符串常量池中,而字符串常量池是存在堆中的。因为字符串常量也经常需要清理,所以在JDK1.7放到堆中了。

34、什么是PLAB?
PLAB与TLAB思想类似,不过用于老年代。当多线程并发执行Minor GC时,可能有很多对象需要晋升到老年代,此时,每个线程会从老年代申请一块空间,在空间中使用指针加法来分配内存,提高效率。

35、JVM新生代回收如何避免全堆扫描?
堆区其实被分成一块一块的card page,有一个card table是一个字节数组,每个元素对应一块card page。如果老年代中有引用指向新生代某一个card page中的对象,那么老年代中此对象所在card page在card table中对应位置会被标记成dirty。在新生代垃圾回收时,只需扫描card table,找到有跨代引用的老年代对象的card page,扫描其中的老年代对象,再根据引用找到新生代对象进行标记。

36、java的CMS和G1垃圾回收器在card table维护上有什么不同?
CMS使用card table优化Minor GC,不用扫描整个堆区。但是在老年代垃圾回收时派不上用场。而G1是将堆区划分成region,每个region中有一个RSet存储其他Region对这个region对象的引用。RSet是hash table结构,key存储的是引用所在的其他region,value是一个集合,存储着card table的index。无论进行Minor GC或者Mixed GC时,都是通过RSet找到card table上的index,再找到对应的card page进行扫描。