JVM面试题学习
内容部分来源沉默王二、小林coding、代码随想录
一、面试题
1、说说JVM
JVM是Java虚拟机,是Java程序实现跨平台的基石。JVM会将字节码文件翻译为机器语言从而让本地能够运行Java程序。
JVM有很多不错的特性,比如它可以进行垃圾回收自动管理内存,并且它有一个即时编译器,将热点代码缓存到CodeCache中提高执行效率。
2、Jvm的组织架构
首先是类加载器,负责读入二进制文件,并将数据读入到内存中。、
然后是运行时数据区,按照虚拟机规范来划分区域便于管理。有方法区、堆、虚拟机栈,本地方法栈,程序计数器。
最后是执行引擎负责执行字节码,垃圾回收等,以及JNI与本地库进行交互
3、说一下Jvm的内存区域
内存区域分为方法区、堆、虚拟机栈、本地方法栈、程序计数器
其中方法区和堆是线程共享的,而虚拟机栈,本地方法栈,程序计数器是线程私有的
程序计数器放的是Jvm的指令地址
虚拟机栈存储线程执行方法的栈帧,
当一个 Java 程序调用一个 native 方法时,JVM 会切换到本地方法栈来执行这个方法
堆是JVM内存中最大的一块区域,主要存储对象,堆也是垃圾收集器的主要管理目标
方法区用于存储已被 JVM 加载的类信息、常量、静态变量等。在 HotSpot 虚拟机中,方法区的实现称为永久代 但在 Java 8 及之后的版本中,已经被元空间所替代。
4、 堆和栈的主要区别
堆属于线程共享的内存区域,几乎所有 new 出来的对象都会堆上分配,生命周期不由单个方法调用所决定,直到不再被任何变量引用被垃圾收集器回收。堆的空间一般较大
栈属于线程私有的内存区域,主要存储局部变量、方法参数、对象引用等,通常随着方法调用的结束而自动释放。栈的空间一般较小,而且存取速度也比堆快
默认情况下,Java 对象是在堆中分配的,但 JVM 会判断对象的生命周期是否只在方法内部,如果是的话,这个对象可以在栈上分配。
5、 为什么使用元空间替代永久代
永久代会导致 Java 应用程序更容易出现内存溢出的问题,因为它要受到 JVM 内存大小的限制。而元空间放到了内存里,只要内存够就可以用
6、对象创建和销毁的过程说一说
new一个对象时,会触发类加载机制,类未被加载就会执行。然后再分配内存初始化,然后设置对象头,最后再执行构造方法。
这里堆中分配主要是指针碰撞和空闲链表。
指针碰撞就是指针指向未分配的空间,需要存放我就移动指针来放
空闲链表是JVM维护一个链表记录未占用的内存块,寻找合适的块来放对象
销毁就是当对象不再被任何引用指向时就会变成垃圾,垃圾收集器通过可达性分析判断是否存活并进行回收。
7、内存分配会出现竞争吗?(TLAB是什么,说一下)
jvm采用TLABl来解决多线程竞争堆内存的分配问题
TLAB就是线程本地分配缓冲区,线程分配对象时直接在TLAB分配
8、说一下对象的内存布局
对象在内存中包括三部分:对象头、实例数据和对齐填充。
对象头是对象存储在内存中的元信息,包含了Mark Word、类型指针等信息。
实例数据是对象实际的字段值,也就是成员变量的值,不过JVM可能会对它进行对齐重排
对齐填充就是要保证8字节对齐,这里对齐主要是为了提高效率,防止CPU跨缓存行访问降低了速度
(new Object()的内存大小在64位操作系统上占16个字节,8字节MarkWord,4字节指针(默认被压缩),4字节填充)
9、说一下对象有哪几种引用?
强引用:使用new关键字赋值的引用就是强引用,只要强引用关联对象,那么对象就不会被回收。
软引用:软引用通过softrefenrence实现,对象在内存不足时会被回收
弱引用:比如ThreadLocal里的entry,弱引用在下一次垃圾回收时会被回收,无论内存是否充足。
虚引用:主要用来跟踪对象被垃圾回收的过程,在任何时候都可能被回收
10、堆的内存分区了解吗?
Java堆分为新生代和老年代,新生代又分为eden和survivor,survivor分为from和to
新创建的对象会被分配到 Eden 空间。当 Eden 区填满时,会触发一次 Minor GC,清除不再使用的对象。存活下来的对象会从 Eden 区移动到 Survivor 区。
对象在新生代中经历多次 GC 后,如果仍然存活,会被移动到老年代。当老年代内存不足时,会触发 Major GC,对整个堆进行垃圾回收。
11、说一下新生代 的划分和原理?
新生代的垃圾收集主要采用标记-复制算法,因为新生代的存活对象比较少,每次复制少量的存活对象效率比较高。
发生垃圾收集时,将 Eden 和 Survivor 中仍然存活的对象一次性复制到另外一块 Survivor 空间上,然后直接清理掉 Eden 和已用过的那块 Survivor 空间。内存比为8:1:1
12、对象什么时候进入老年代?
1、 JVM 会为对象维护一个“年龄”计数器,记录对象在新生代中经历 Minor GC 的次数。每次 GC 未被回收的对象其年龄会加 1。
当超过一个特定阈值,默认值是 15,就会被认为老对象了,然后放入老年代
2、还有就是大对象,如大数组长字符串等,在G1 垃圾收集器中 当对象大小超过一个 Region 容量的 50% 时,会被认为是大对象直接放入老年代中
3、还有就是动态年龄判定,如果某年龄的对象总大小超过 Survivor 区的一半,就允许这些对象直接晋升老年代,避免频繁复制浪费时间
13、什么是STW?
STW是stop the world , JVM 进行垃圾回收的过程中 为了保证对象引用在移动过程中不被修改,必须暂停所有的用户线程
JVM 会使用一个名为安全点的机制来确保线程能够被安全地暂停
14、说说OOM
OOM是内存溢出,指当程序请求分配内存时,由于没有足够的内存空间。
除此之外还有一个概念是内存泄漏,内存泄漏是指程序在使用完内存后,未能及时释放,导致占用的内存无法再被使用。随着时间的推移,内存泄漏会导致可用内存逐渐减少,最终导致内存溢出。
内存泄漏通常由连接资源未释放、静态集合未回收导致的
内存溢出通常由于大对象的集合,或递归调用导致的
(排查可以使用jdk自带的jstack 等命令行工具进行了排查,)
解决普通回答:使用ThreadLocal时,key为null的Entry无法被回收,会造成内存泄漏甚至导致内存溢出,可以采用remove方法或set(null)来处理
15、 讲讲JVM的垃圾回收机制
jvm会先用可达性分析算法来判断哪些对象可以回收,然后垃圾收集器,采用适当的垃圾回收算法来回收对象。
16、如何判断对象仍然存活?
jvm使用可达性分析算法来判断,通过一组GCroots根对象进行递归扫描,而在垃圾回收前,jvm会暂停所有线程(STW)。
GCroots包括虚拟机栈和本地方法栈中的引用,类静态变量、运行常量池的常量
17、finalize方法了解不
如果对象在进行可达性分析后发现没有与 GC Roots 相连接的引用链,那它将会被第一次标记并执行
finalize()
方法。如果对象在finalize()
中成功与引用链上的任何一个对象建立关联,那么就不会回收
18、垃圾收集算法说一说
1、标记清除算法:标记要回收的对象来清楚它们,但易产生内存碎片
2、标记复制算法:将内存空间划为两块,当一块用完了就将活着的对象复制到另一块上面,但这样浪费了一半的空间
3、标记整理算法:将存活的对象向内存的一端移动,再清理边界以外的内存,缺点是移动的成本高
4、目前主流的垃圾收集算法就是分代收集算法,将堆分为新生代和老年代,新生代用标记复制算法,老年代用标记整理算法。,这样根据生命周期优化垃圾回收
19、Minor GC、Major GC、Mixed GC、Full GC 都是什么意思?
minorGC是指发生在年轻代的垃圾收集,G1支持
majorGc是老年代的垃圾回收,是CMS特有的
MixedGC 同时清理老年代、年轻代,是G1特有的
Full GC 是最彻底的垃圾收集,涉及整个 Java 堆和方法区。它是最耗时的 GC,通常在 JVM 压力很大时发生(分代收集清理过程)
20、 知道哪些垃圾收集器?
JVM 的垃圾收集器主要分为两大类:分代收集器和分区收集器,分代收集器的代表是 CMS,分区收集器的代表是 G1 和 ZGC。
1、CMS 是一种低延迟的垃圾收集器,采用标记-清除算法,分为初始标记、并发标记、重新标记和并发清除四个阶段,优点是垃圾回收线程和应用线程同时运行,停顿时间短.但容易产生内存碎片,可能触发 Full GC。
2、G1 是一种面向大内存、高吞吐场景的垃圾收集器,它将堆划分为多个小的 Region,通过标记-整理算法,避免了内存碎片问题。但调优较复杂
3、ZGC是 JDK 11 时引入的一款低延迟的垃圾收集器,最大特点是将垃圾收集的停顿时间控制在 10ms 以内,它通过并发标记和重定位来避免大部分 Stop-The-World 停顿,主要依赖指针染色来管理对象状态
此外还有serial\serial old\parnew\parallel \parallelold 。
21、说一下CMS的垃圾回收过程
CMS 使用标记-清除算法进行垃圾收集,分 4 大步:
- 初始标记:标记所有从 GC Roots 直接可达的对象,这个阶段需要 STW,但速度很快。
- 并发标记:从初始标记的对象出发,遍历所有对象,标记所有可达的对象。这个阶段是并发进行的。
- 重新标记:完成剩余的标记工作,包括处理并发阶段遗留下来的少量变动,这个阶段通常需要短暂的 STW 停顿。
- 并发清除:清除未被标记的对象,回收它们占用的内存空间
22、说一下G1垃圾收集器的原理
23、CMS和G1有什么区别
24、说一下类加载过程
类装载过程包括三个阶段:载入、链接和初始化。
①、载入:将类的二进制字节码加载到内存中。
②、链接可以细分为三个小的阶段:
- 验证:检查类文件格式是否符合 JVM 规范
- 准备:为类的静态变量分配内存并设置默认值。
- 解析:将符号引用替换为直接引用。
③、初始化:执行静态代码块和静态变量初始化。
25、什么是双亲委派机制?
该机制的核心思想是:如果一个类加载器收到了类加载请求,默认先将该请求委托给其父类加载器处理。只有当父级加载器无法加载该类时,才会尝试自行加载。
启动类加载器,负责加载 JVM 的核心类库
扩展类加载器,负责加载
JAVA_HOME/jre/lib/ext
目录下,或者由系统属性java.ext.dirs
指定位置的类库,由sun.misc.Launcher$ExtClassLoader
实现。应用程序类加载器,负责加载 classpath 的类库
我们编写的任何类都是由应用程序类加载器加载的,除非显式使用自定义类加载器。
用户自定义类加载器,通常用于加载网络上的类、执行热部署
这些类从上到下依次为父子关系。
这样可以避免类的重复加载,保证核心类库的安全性
重写 ClassLoader 的
loadClass()
方法可以破坏该机制