JVM 相关面试题整理!没有一句废话,字字都是精辟!!
-
- 1.JVM 有哪些内存区域?(JVM 内存模型?)
- 2.栈帧都有哪些数据?
- 3.Java 的内存模型是什么?(JMM 是什么?)
- 4.JIT 是什么?
- 5.Java 的双亲委派机制是什么?
- 6.类加载有几个过程?
- 7.有哪些打破了双亲委托机制的案例?
- 8.JVM 垃圾回收时如何确定垃圾?什么是 GC Roots?
- 9.能够找到 Reference Chain 的对象,就一定会存活么?
- 10.强引用、软引用、弱引用、虚引用是什么?
- 11.谈谈对 OOM 的认识?
- 12.什么情况下会发生栈溢出?
- 13.什么情况会造成元空间溢出?
- 14.什么时候会造成堆外内存溢出?
- 15.HashMap 中的 key,可以是普通对象么?有什么需要注意的地方?
- 16.常见 GC 回收算法
- 17.常见垃圾收集器?
- 18.对象是怎么从年轻代进入老年代的?
- 19.CMS 分为哪几个阶段?
- 20.CMS 都有哪些问题?
- 21.对于 JDK 自带的监控和性能分析工具用过哪些?
- 22.MinorGC、MajorGC、FullGC 都什么时候发生?
1.JVM 有哪些内存区域?(JVM 内存模型?)
线程隔离的有:
- 程序计数器
- 虚拟机栈
- 本地方法栈
线程共享的有:
- 堆
- 方法区
程序计数器用于流程控制和多线程环境下的记录当前线程的指令位置,程序计数器也是唯一一个不会出现 OOM 的内存区域
虚拟机栈由一个个栈帧组成,其中的局部变量表用于存放方法参数和方法内的局部变量
本地方法栈是为一些 Native 方法服务的
堆用于存放对象实例,是占用内存区域最大的一块空间,也是垃圾收集器管理的主要区域,分为新生代和老年代
方法区用于存储已被虚拟机加载的类信息、常量、静态变量和即时编译器编译后的代码等
2.栈帧都有哪些数据?
栈帧中包含 局部变量表、操作数栈、动态连接,返回地址等。
3.Java 的内存模型是什么?(JMM 是什么?)
JMM是Java内存模型,是一种规范,它定义了Java程序中线程如何与内存交互,分为主内存和工作内存
- 主内存:所有线程共享的内存区域,存储实例字段和静态字段。
- 工作内存:每个线程的私有内存区域,存储线程的变量副本。
如何访问变量?
- 读取变量:线程先从工作内存读取,然后操作。
- 写入变量:线程先将值写入工作内存,然后写回主内存。
关键特性?
- 原子性:操作不可分割,确保同时只能有一个线程执行。
- 可见性:修改后的变量对其他线程立即可见。
- 有序性:程序执行顺序按照代码顺序执行。
解决可见性问题?
- 使用volatile关键字。
- 使用synchronized关键字。
- 使用final关键字。
3.1 说说重排序问题
为了优化程序执行效率,编译器可能会改变代码的执行顺序。
在单线程环境中,重排序通常不影响程序的执行结果。但在多线程环境中,可能导致线程安全问题。
volatile 通过内存屏障解决重排序问题,保证volatile 写之前的操作不会被编译器重排序到 volatile 写之后,volatile 读之后的操作不会被编译器重排序到 volatile 读之前
4.JIT 是什么?
JIT,即时编译器,为了提高热点代码的执行效率,在运行时,会把这些代码编译成与本地平台相关的机器码,并进行各种层次的优化
5.Java 的双亲委派机制是什么?
双亲委派的意思是,除了顶层的启动类加载器以外,其余的类加载器,在加载前,都会委派给它的父加载器进行加载,这样这样一层层向上传递,知道祖先们都无法胜任的时候,它才会真正加载
6.类加载有几个过程?
加载、验证、准备、解析、初始化。
7.有哪些打破了双亲委托机制的案例?
Tomcat 可以加载自己目录下的 class 文件,并不会传递给父类的加载器;
Java 的 SPI,发起者是 BootstrapClassLoader,BootstrapClassLoader 已经是最上层了,它直接获取了 AppClassLoader 进行驱动加载,和双亲委派是相反的。
8.JVM 垃圾回收时如何确定垃圾?什么是 GC Roots?
JVM 采用的是 可达性分析算法,JVM 通过 GC Roots 判断对象是否存活。
GC Roots 向下追溯、搜索,会产生一条 Chain Reference 的链,如果一个对象不能和 GC Roots 产生任何关系,那他会被判定为垃圾,会被回收
GC Roots 大体包括:
- 活动线程相关的各种引用,比如虚拟机栈中栈帧的引用
- 类的静态变量引用
- JNI 引用等
9.能够找到 Reference Chain 的对象,就一定会存活么?
不一定,还要看引用类型,软引用会在内存不足时被回收,弱引用会在 GC 时被回收,但是如果没有 Reference Chain 的对象,一定会被回收
10.强引用、软引用、弱引用、虚引用是什么?
普通的对象引用关系就是强引用
软引用一般用于维护一些可有可无的对象,只有在内存不足时,系统会回收软引用对象,如果回收了软引用对象之后,内存仍然不足,就会抛出OOM
弱引用对象相比软引用来说,要更加无用一些,它拥有更短的生命周期,当 JVM 进行垃圾回收时,无论内存是否充足,都会回收被弱引用关联的对象。
虚引用是一种形同虚设的引用,在现实场景中用的不是很多,它主要用来跟踪对象被垃圾回收的活动。
11.谈谈对 OOM 的认识?
OOM 是非常严重的问题,除了程序计数器,其他内存区域都有溢出的风险,最多的就是堆溢出,另外,元空间在加载的类非常多的情况下也会溢出,还有就是栈溢出,这个通常影响比较小。
堆内存可能是对象过多或者内存泄漏,也可能是访问量过多
元空间溢出可能是加载的类非常多,例如使用 JDK 反射 或者 CGLIB 直接操作字节码在运行时就会产生大量的类
栈溢出可能是递归调用过深或者线程创建过多
详情可以参考这篇文章:深入解析发生 OOM 的三大场景
12.什么情况下会发生栈溢出?
栈的大小可以通过 -Xss 参数进行设置,当递归层次太深或者线程创建过多的时候,则会发生栈溢出。
13.什么情况会造成元空间溢出?
元空间默认是没有上限的,不加限制比较危险。当应用中的 Java 类过多时,比如 Spring 等一些使用动态代理的框架生成了很多类,如果占用空间超出了我们的设定值,就会发生元空间溢出。
14.什么时候会造成堆外内存溢出?
使用了 Unsafe 类申请内存,或者使用了 JNI 对内存进行操作,这部分内存是不受 JVM 控制的,不加限制使用的话,会很容易发生内存溢出。
15.HashMap 中的 key,可以是普通对象么?有什么需要注意的地方?
Map 的 key 和 value 可以是任何类型,但要注意的是,一定要重写它的 equals 和 hashCode 方法,否则容易发生内存泄漏。
16.常见 GC 回收算法
标记清除、复制清除、标记整理、分类收集算法
- 标记清除:找出活跃的对象,将未被标记的对象回收清除掉
- 复制清除:半区复制,将内存分为大小相同的两块,每次使用其中一块,当用完后,就将存活的对象赋值到另一块儿去,再把使用的空间一次清理掉
- 标记整理:找出活跃的对象,将活跃对象向一端移动,然后直接清理掉边界以外的内存
- 分类收集算法:将堆划分为新生代和老年代,新生代使用复制算法,老年代使用标记清除或者标记整理
17.常见垃圾收集器?
很多垃圾回收器都是分代回收的:
- 对于年轻代,主要有 Serial、ParNew 等垃圾回收器,回收过程主要使用复制算法;
- 老年代的回收算法有 Serial、CMS 等,主要使用标记清除、标记整理算法等。
我们使用较多的是 G1,也有年轻代和老年代的概念,不过它是一个整堆回收器,它的回收对象是小堆区 。
18.对象是怎么从年轻代进入老年代的?
在下面 4 种情况下,对象会从年轻代进入到老年代。
- 如果对象够老,则会通过提升(Promotion)的方式进入老年代,一般根据对象的年龄进行判断。
- 动态对象年龄判定,有的垃圾回收算法,比如 G1,并不要求 age 必须达到 15 才能晋升到老年代,它会使用一些动态的计算方法。
- 分配担保,当 Survivor 空间不够的时候,则需要依赖其他内存(指老年代)进行分配担保,这个时候,对象也会直接在老年代上分配。
- 超出某个大小的对象将直接在老年代上分配,不过这个值默认为 0,意思是全部首选 Eden 区进行分配。
19.CMS 分为哪几个阶段?
- 初始标记
- 并发标记
- 重新标记
- 并发清除
20.CMS 都有哪些问题?
- 内存碎片问题, Full GC 的整理阶段,会造成较长时间的停顿
- 需要预留空间,用来分配收集阶段产生的“浮动垃圾”
- 使用更多的 CPU 资源,在应用运行的同时进行堆扫描
- 停顿时间不可预期
21.对于 JDK 自带的监控和性能分析工具用过哪些?
- jps:用来显示 Java 进程;
- jstat:用来查看 GC;
- jmap:用来 dump 堆;
- jstack:用来 dump 栈;
- jhsdb:用来查看执行中的内存信息。
22.MinorGC、MajorGC、FullGC 都什么时候发生?
MinorGC 在年轻代空间不足的时候发生
MajorGC 指的是老年代的 GC,出现 MajorGC 一般经常伴有 MinorGC。
FullGC 有三种情况:
- 第一,当老年代无法再分配内存的时候;
- 第二,元空间不足的时候;
- 第三,显示调用 System.gc 的时候。