1. JVM内存模型
1.1 运行时数据区
-
程序计数器:线程私有,记录当前线程执行的字节码行号指示器
-
Java虚拟机栈:线程私有,存储栈帧(局部变量表、操作数栈、动态链接、方法出口等)
-
本地方法栈:为Native方法服务
-
Java堆:所有线程共享,存放对象实例,是GC主要区域
-
方法区:存储已被加载的类信息、常量、静态变量等
1.2 内存分配策略
-
对象优先在Eden区分配
-
大对象直接进入老年代
-
长期存活的对象进入老年代(默认15次GC后晋升)
-
动态对象年龄判定(Survivor区中相同年龄对象总大小超过一半,则大于等于该年龄的对象直接进入老年代)
2. 垃圾回收机制
2.1 判断对象是否可回收
-
引用计数法:简单但无法解决循环引用问题
-
可达性分析算法:通过GC Roots对象作为起点,判断对象引用链是否可达
2.2 垃圾回收算法
-
标记-清除:简单但会产生内存碎片
-
复制算法:将内存分为两块,每次使用一块,回收时将存活对象复制到另一块
-
标记-整理:标记后让存活对象向一端移动,然后清理边界外内存
-
分代收集:根据对象存活周期将堆分为新生代和老年代,采用不同算法
2.3 常见垃圾收集器
-
Serial:单线程,适用于客户端应用
-
ParNew:Serial的多线程版本
-
Parallel Scavenge:吞吐量优先的收集器
-
CMS:低停顿时间的收集器,采用标记-清除算法
-
G1:面向服务端的收集器,整体采用标记-整理算法,局部采用复制算法
3. 类加载机制
3.1 类加载过程
-
加载:获取类的二进制字节流
-
验证:确保Class文件符合规范
-
准备:为类变量分配内存并设置初始值
-
解析:将符号引用转为直接引用
-
初始化:执行类构造器<clinit>()方法
3.2 类加载器
-
启动类加载器:加载<JAVA_HOME>/lib目录下的类
-
扩展类加载器:加载<JAVA_HOME>/lib/ext目录下的类
-
应用程序类加载器:加载用户类路径上的类
-
自定义类加载器:用户自定义实现
3.3 双亲委派模型
-
子类加载器先委托父类加载器加载
-
父类加载器无法完成加载时,子类加载器才尝试自己加载
-
优点:避免重复加载,保证核心类安全
4. JVM性能调优
4.1 常用JVM参数
-
-Xms:初始堆大小 -
-Xmx:最大堆大小 -
-Xmn:新生代大小 -
-XX:SurvivorRatio:Eden与Survivor区比例 -
-XX:NewRatio:新生代与老年代比例 -
-XX:MaxTenuringThreshold:对象晋升老年代的年龄阈值
4.2 调优工具
-
jps:查看Java进程
-
jstat:监控JVM统计信息
-
jmap:生成堆转储快照
-
jstack:生成线程快照
-
VisualVM:可视化监控工具
-
MAT:内存分析工具
5. 常见面试问题
5.1 JVM内存结构
JVM内存主要分为以下几个区域:
-
方法区(Method Area):存储类信息、常量、静态变量等数据
-
堆(Heap):对象实例的存储区域,是GC主要管理的区域
-
虚拟机栈(VM Stack):线程私有,存储局部变量表、操作数栈等
-
本地方法栈(Native Method Stack):为本地方法服务
-
程序计数器(Program Counter Register):线程私有,记录当前线程执行的位置
5.2 对象回收判断
判断对象是否可回收的算法:
-
引用计数法:每个对象维护引用计数器,为0时回收。简单但无法解决循环引用问题。
-
可达性分析:从GC Roots对象开始遍历引用链,不可达的对象判定为可回收。GC Roots包括:
-
虚拟机栈中引用的对象
-
方法区中类静态属性引用的对象
-
方法区中常量引用的对象
-
本地方法栈中JNI引用的对象
-
5.3 垃圾回收算法
标记-清除算法(Mark-Sweep)
-
过程:标记所有需要回收的对象,然后统一清除
-
优点:实现简单
-
缺点:产生内存碎片,效率不高
复制算法(Copying)
-
过程:将内存分为两块,每次只使用一块,存活对象复制到另一块
-
优点:无内存碎片
-
缺点:内存利用率只有50%
标记-整理算法(Mark-Compact)
-
过程:标记后让存活对象向一端移动,然后清理边界外的内存
-
优点:无内存碎片,内存利用率高
-
缺点:移动对象成本高
分代收集算法(Generational Collection)
-
新生代:使用复制算法(如Serial、ParNew收集器)
-
老年代:使用标记-清除或标记-整理算法(如CMS、Serial Old收集器)
5.4 CMS与G1收集器比较
CMS(Concurrent Mark Sweep)
-
特点:以最短回收停顿时间为目标,标记-清除算法
-
过程:
-
初始标记(STW)
-
并发标记
-
重新标记(STW)
-
并发清除
-
-
优点:并发收集,低停顿
-
缺点:产生内存碎片,CPU敏感
G1(Garbage-First)
-
特点:面向服务端应用,整体基于标记-整理,局部基于复制算法
-
优点:
-
可预测停顿时间
-
高吞吐量
-
无内存碎片问题
-
-
适用场景:大内存(6GB以上)应用
5.5 双亲委派模型
类加载器层次结构:
-
启动类加载器(Bootstrap ClassLoader):加载<JAVA_HOME>/lib目录
-
扩展类加载器(Extension ClassLoader):加载<JAVA_HOME>/lib/ext目录
-
应用程序类加载器(Application ClassLoader):加载用户类路径
工作流程:子类加载器先委托父类加载器加载,父类无法完成时子类才尝试加载
优点:
-
避免重复加载
-
保证核心类库安全(如java.lang.Object只能由启动类加载器加载)
5.6 内存泄漏排查
排查步骤:
-
监控工具:使用jstat、VisualVM等工具观察内存变化
-
堆转储 :
jmap -dump:format=b,file=heap.hprof <pid> -
分析工具:MAT(Memory Analyzer Tool)、JProfiler分析堆转储文件
-
常见泄漏点:
-
静态集合类
-
未关闭的资源(连接、流等)
-
监听器未注销
-
不合理的缓存设计
-
5.7 JVM调优参数
常用参数:
- 堆内存 :
-Xms:初始堆大小-Xmx:最大堆大小-Xmn:新生代大小
- GC相关 :
-XX:+UseConcMarkSweepGC:使用CMS收集器-XX:+UseG1GC:使用G1收集器-XX:MaxGCPauseMillis:最大GC停顿时间目标
- 其他 :
-XX:MetaspaceSize:元空间初始大小-XX:MaxMetaspaceSize:元空间最大大小-XX:+HeapDumpOnOutOfMemoryError:OOM时自动生成堆转储
5.8 线程死锁分析
排查方法:
-
获取线程快照 :
jstack <pid> > thread_dump.log -
分析死锁:查找"Found one Java-level deadlock"部分
-
工具分析:使用VisualVM、JConsole等工具查看线程状态
-
预防措施:
-
按固定顺序获取锁
-
使用tryLock设置超时
-
避免嵌套锁
-
5.9 对象创建过程
-
类加载检查:检查类是否已加载
-
内存分配:
-
指针碰撞:内存规整时使用
-
空闲列表:内存不规整时使用
-
-
初始化零值:为对象字段赋默认值
-
设置对象头:存储类元数据、GC年龄等信息
-
执行<init>方法:按照程序员的意愿初始化对象
5.10 类加载过程
-
加载(Loading):
-
获取类的二进制字节流
-
将静态存储结构转化为方法区运行时数据结构
-
生成Class对象作为访问入口
-
-
验证(Verification):确保类文件符合规范
-
准备(Preparation):为类变量分配内存并设置初始值
-
解析(Resolution):将符号引用转为直接引用
-
初始化(Initialization):执行类构造器<clinit>()方法
-
使用(Using):程序中使用已加载的类
-
卸载(Unloading):从JVM中移除不再需要的类