JVM是Java面试的核心模块,涉及内存模型、垃圾回收、类加载等关键知识点。
1. JVM内存区域划分?
- 程序计数器:当前线程执行的字节码行号,线程私有,唯一不会OOM的区域。
- 虚拟机栈:线程私有,存储栈帧(局部变量表、操作数栈、动态链接、方法出口)。-Xss设置大小。
- 本地方法栈:服务Native方法,线程私有。
- 堆:线程共享,存放对象实例和数组。-Xms和-Xmx设置大小,GC主要区域。
- 方法区:线程共享,存放类信息、常量、静态变量。JDK8后用元空间(Metaspace)实现,使用本地内存。
2. 对象创建过程?
- 类加载检查:检查常量池中类的符号引用是否已加载、解析、初始化。
- 分配内存:指针碰撞(内存规整)或空闲列表(内存碎片)。
- 初始化零值:内存分配后初始化为零值(不包括对象头)。
- 设置对象头:设置Mark Word(哈希码、GC分代年龄、锁状态)和类元信息引用。
- 执行init:执行构造方法,为属性赋值。
3. 如何判断对象是否存活?
- 引用计数法:对象被引用+1,取消-1,为0可回收。缺点:循环引用问题,JVM不采用。
- 可达性分析:从GC Roots开始遍历对象图,不可达的对象为可回收对象。JVM采用此方法。
GC Roots包括:虚拟机栈中引用的对象、方法区中类静态属性引用的对象、方法区中常量引用的对象、本地方法栈JNI引用的对象、Java虚拟机内部引用、同步锁持有的对象。
4. JVM垃圾回收算法?
- 标记-清除:标记存活对象,清除其余。缺点:产生内存碎片。
- 标记-整理:标记存活对象,向一端移动,清除边界外。无碎片,但移动开销大,适合老年代。
- 复制算法:将内存分两块,存活对象复制到另一块,清空当前。无碎片,但利用率50%。适合新生代。
- 分代收集:新生代用复制算法(Eden:Survivor=8:1:1),老年代用标记-清除或标记-整理。
5. 常见垃圾收集器?
- Serial / Serial Old:单线程,STW,适合Client模式。
- Parallel Scavenge / Parallel Old:多线程并行,吞吐量优先,适合后台运算。
- CMS:老年代收集器,标记-清除,低延迟。初始标记(STW)→并发标记→重新标记(STW)→并发清除。已废弃。
- G1:将堆分为Region,混合回收。可控停顿时间(-XX:MaxGCPauseMillis)。JDK9默认。
- ZGC:着色指针+读屏障,停顿时间<10ms,支持TB级堆。JDK11引入。
6. 类加载过程?
- 加载:通过类全限定名获取二进制字节流,生成Class对象。
- 验证:文件格式、元数据、字节码、符号引用验证。
- 准备:为静态变量分配内存并赋零值(final修饰的在编译期赋值)。
- 解析:将常量池中的符号引用替换为直接引用。
- 初始化:执行类构造器clinit()方法,为静态变量赋值,执行静态代码块。
7. 双亲委派模型及打破场景?
双亲委派:类加载请求先委托给父加载器,父加载器无法加载时才自己加载。
- 启动类加载器:加载JAVA_HOME/lib。
- 扩展类加载器:加载JAVA_HOME/lib/ext。
- 应用类加载器:加载classpath。
- 自定义类加载器:继承URLClassLoader。
打破场景:SPI机制用Thread.contextClassLoader打破(JDBC);热部署Tomcat每个webapp有独立ClassLoader;OSGi网状结构不遵循双亲委派。
8. JVM调优常用参数?
- 堆大小:-Xms4g -Xmx4g(建议相同,避免扩容抖动)。
- 新生代:-Xmn2g 或 -XX:NewRatio=2(老:新=2:1)。
- 元空间:-XX:MetaspaceSize=256m -XX:MaxMetaspaceSize=512m。
- GC日志:-Xlog:gc*:file=gc.log(JDK9+)。
- 收集器:-XX:+UseG1GC。
- OOM时dump:-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/tmp/heapdump.hprof。
9. OOM类型及排查思路?
- java.lang.OutOfMemoryError: Java heap space:堆内存不足,可能内存泄漏或大对象。jmap -dump导出heap,MAT分析。
- GC overhead limit exceeded:GC耗时>98%但回收<2%。通常是内存泄漏。
- Metaspace:元空间溢出,动态生成类过多(如CGLIB)。增大MaxMetaspaceSize。
- Direct buffer memory:堆外内存不足,NIO使用不当。
- Unable to create new native thread:线程数超过系统限制。
10. 强引用、软引用、弱引用、虚引用?
- 强引用:Object obj = new Object(),只要强引用存在就不回收。
- 软引用:SoftReference,内存不足时才回收。适合缓存。
- 弱引用:WeakReference,下一次GC就回收。ThreadLocalMap的key。
- 虚引用:PhantomReference,不影响对象生命周期,仅用于跟踪回收事件。配合ReferenceQueue。
11. 内存泄漏与内存溢出的区别?
内存泄漏:对象不再使用但无法被GC回收,如集合中存放对象引用但未移除。长期积累导致内存溢出。
内存溢出(OOM):程序申请内存时没有足够内存。可能是泄漏导致,也可能是堆大小设置不足或瞬时大对象。
