JVM是Java的核心,掌握JVM原理是成为Java高级工程师的必经之路。本文将深入剖析JVM的内存模型、垃圾回收机制和性能调优技巧,助你彻底理解Java底层运行机制。
一、JVM内存结构
1.1 运行时数据区
关键点: JVM内存分为线程共享和线程私有两大类。
JVM内存结构
├── 线程共享
│ ├── 方法区(Method Area)- 存储类信息、常量、静态变量
│ └── 堆(Heap)- 存储对象实例
│
└── 线程私有
├── 程序计数器(PC Register)- 记录当前线程执行位置
├── 虚拟机栈(VM Stack)- 存储局部变量、方法调用
└── 本地方法栈(Native Method Stack)- 执行Native方法
内存区域详解:
java
public class JVMMemoryDemo {
// 1. 方法区:存储类信息
private static int staticVar = 100; // 静态变量
private static final String CONSTANT = "常量"; // 常量
// 2. 堆:存储对象实例
private String name = "张三"; // 实例变量
private int age = 25;
public void method() {
// 3. 虚拟机栈:存储局部变量
int localVar = 10; // 局部变量
String str = "hello"; // 局部变量
// 4. 程序计数器:记录执行位置
for (int i = 0; i < 10; i++) {
System.out.println(i);
}
}
public static void main(String[] args) {
// 对象在堆中创建
JVMMemoryDemo demo = new JVMMemoryDemo();
demo.method();
}
}
1.2 堆内存详解
关键点: 堆是JVM中最大的内存区域,所有对象实例都在这里分配。
堆内存结构(JDK 8+)
├── 新生代(Young Generation)- 33%
│ ├── Eden区 - 80%
│ ├── Survivor0(From)- 10%
│ └── Survivor1(To)- 10%
│
└── 老年代(Old Generation)- 67%
java
// 对象分配示例
public class HeapDemo {
public static void main(String[] args) {
// 1. 小对象在Eden区分配
byte[] small = new byte[1024]; // 1KB
// 2. 大对象直接进入老年代
byte[] large = new byte[10 * 1024 * 1024]; // 10MB
// 3. 长期存活的对象进入老年代
List<String> list = new ArrayList<>();
for (int i = 0; i < 1000000; i++) {
list.add("item" + i);
}
}
}
// JVM参数设置
// -Xms512m 初始堆大小
// -Xmx1024m 最大堆大小
// -Xmn256m 新生代大小
// -XX:SurvivorRatio=8 Eden:Survivor = 8:1
1.3 方法区(元空间)
关键点: JDK 8之前叫永久代,JDK 8之后改为元空间(Metaspace)。
java
// 方法区存储内容
public class MethodAreaDemo {
// 1. 类信息
private String name;
private int age;
// 2. 静态变量
private static int count = 0;
// 3. 常量
private static final String CONSTANT = "常量";
// 4. 方法信息
public void method() {
System.out.println("方法");
}
}
// JVM参数
// -XX:MetaspaceSize=128m 元空间初始大小
// -XX:MaxMetaspaceSize=256m 元空间最大大小
二、垃圾回收机制
2.1 如何判断对象可回收?
关键点: 使用可达性分析算法,从GC Roots开始遍历。
java
/**
* GC Roots包括:
* 1. 虚拟机栈中引用的对象
* 2. 方法区中静态变量引用的对象
* 3. 方法区中常量引用的对象
* 4. 本地方法栈中引用的对象
*/
public class GCRootsDemo {
// GC Root 2:静态变量
private static User staticUser = new User("静态用户");
// GC Root 3:常量
private static final User CONSTANT_USER = new User("常量用户");
public void method() {
// GC Root 1:局部变量
User localUser = new User("局部用户");
// 不是GC Root
User tempUser = new User("临时用户");
tempUser = null; // 可以被回收
}
}
// 引用类型
public class ReferenceDemo {
public static void main(String[] args) {
// 1. 强引用:不会被回收
Object strong = new Object();
// 2. 软引用:内存不足时回收
SoftReference<byte[]> soft = new SoftReference<>(new byte[1024 * 1024]);
// 3. 弱引用:GC时回收
WeakReference<Object> weak = new WeakReference<>(new Object());
// 4. 虚引用:随时可能被回收
PhantomReference<Object> phantom = new PhantomReference<>(
new Object(), new ReferenceQueue<>()
);
}
}
2.2 垃圾回收算法
java
/**
* 1. 标记-清除算法(Mark-Sweep)
* 优点:简单
* 缺点:产生内存碎片
*
* 2. 标记-复制算法(Mark-Copy)
* 优点:无碎片,效率高
* 缺点:浪费一半内存
* 应用:新生代
*
* 3. 标记-整理算法(Mark-Compact)
* 优点:无碎片,不浪费内存
* 缺点:效率较低
* 应用:老年代
*
* 4. 分代收集算法
* 新生代:标记-复制
* 老年代:标记-整理
*/
// 新生代GC(Minor GC)
public class MinorGCDemo {
public static void main(String[] args) {
// 不断创建对象,触发Minor GC
for (int i = 0; i < 1000; i++) {
byte[] bytes = new byte[1024 * 1024]; // 1MB
}
}
}
// 老年代GC(Major GC / Full GC)
public class MajorGCDemo {
public static void main(String[] args) {
List<byte[]> list = new ArrayList<>();
// 不断添加对象,最终触发Full GC
for (int i = 0; i < 1000; i++) {
list.add(new byte[1024 * 1024]); // 1MB
}
}
}
2.3 垃圾收集器
bash
# 1. Serial收集器(单线程)
-XX:+UseSerialGC
# 2. ParNew收集器(多线程)
-XX:+UseParNewGC
# 3. Parallel Scavenge收集器(吞吐量优先)
-XX:+UseParallelGC
-XX:ParallelGCThreads=4
# 4. CMS收集器(停顿时间优先)
-XX:+UseConcMarkSweepGC
-XX:CMSInitiatingOccupancyFraction=70
# 5. G1收集器(推荐)
-XX:+UseG1GC
-XX:MaxGCPauseMillis=200
-XX:G1HeapRegionSize=16m
# 6. ZGC收集器(JDK 11+,超低延迟)
-XX:+UseZGC
G1收集器详解:
java
/**
* G1(Garbage First)特点:
* 1. 分区收集:将堆分为多个Region
* 2. 可预测停顿:可以指定停顿时间
* 3. 并发标记:减少停顿时间
* 4. 优先回收价值最大的Region
*/
// G1 GC日志分析
// [GC pause (G1 Evacuation Pause) (young), 0.0234567 secs]
// [GC pause (G1 Humongous Allocation) (young) (initial-mark), 0.0345678 secs]
// [GC concurrent-mark-start]
// [GC concurrent-mark-end, 0.1234567 secs]
// JVM参数配置
public class G1Demo {
public static void main(String[] args) {
// -XX:+UseG1GC
// -XX:MaxGCPauseMillis=200
// -XX:G1HeapRegionSize=16m
// -Xms4g -Xmx4g
List<byte[]> list = new ArrayList<>();
for (int i = 0; i < 1000; i++) {
list.add(new byte[1024 * 1024]);
if (i % 100 == 0) {
System.out.println("已分配:" + i + "MB");
}
}
}
}
三、JVM性能调优
3.1 常用JVM参数
bash
# 堆内存设置
-Xms4g # 初始堆大小
-Xmx4g # 最大堆大小(建议与Xms相同)
-Xmn1g # 新生代大小
-XX:SurvivorRatio=8 # Eden:Survivor = 8:1
# 元空间设置
-XX:MetaspaceSize=256m # 元空间初始大小
-XX:MaxMetaspaceSize=512m # 元空间最大大小
# 垃圾收集器
-XX:+UseG1GC # 使用G1收集器
-XX:MaxGCPauseMillis=200 # 最大停顿时间
# GC日志
-Xloggc:gc.log # GC日志文件
-XX:+PrintGCDetails # 打印GC详情
-XX:+PrintGCDateStamps # 打印GC时间戳
# OOM时dump堆
-XX:+HeapDumpOnOutOfMemoryError
-XX:HeapDumpPath=/tmp/heapdump.hprof
# 线程栈大小
-Xss256k # 每个线程栈大小
3.2 内存溢出分析
java
// 1. 堆内存溢出(OutOfMemoryError: Java heap space)
public class HeapOOMDemo {
public static void main(String[] args) {
// -Xms20m -Xmx20m
List<byte[]> list = new ArrayList<>();
while (true) {
list.add(new byte[1024 * 1024]); // 不断添加对象
}
// Exception: java.lang.OutOfMemoryError: Java heap space
}
}
// 2. 栈溢出(StackOverflowError)
public class StackOverflowDemo {
private int count = 0;
public void recursion() {
count++;
recursion(); // 无限递归
}
public static void main(String[] args) {
// -Xss256k
new StackOverflowDemo().recursion();
// Exception: java.lang.StackOverflowError
}
}
// 3. 元空间溢出(OutOfMemoryError: Metaspace)
public class MetaspaceOOMDemo {
public static void main(String[] args) {
// -XX:MaxMetaspaceSize=50m
while (true) {
// 动态生成类
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(Object.class);
enhancer.setCallback(new MethodInterceptor() {
@Override
public Object intercept(Object obj, Method method,
Object[] args, MethodProxy proxy) {
return proxy.invokeSuper(obj, args);
}
});
enhancer.create();
}
// Exception: java.lang.OutOfMemoryError: Metaspace
}
}
// 4. 直接内存溢出(OutOfMemoryError: Direct buffer memory)
public class DirectMemoryOOMDemo {
public static void main(String[] args) {
// -XX:MaxDirectMemorySize=10m
List<ByteBuffer> list = new ArrayList<>();
while (true) {
ByteBuffer buffer = ByteBuffer.allocateDirect(1024 * 1024);
list.add(buffer);
}
// Exception: java.lang.OutOfMemoryError: Direct buffer memory
}
}
3.3 性能分析工具
bash
# 1. jps:查看Java进程
jps -l
# 2. jstat:查看GC统计
jstat -gc <pid> 1000 10 # 每秒输出一次,共10次
jstat -gcutil <pid> # 查看GC百分比
# 3. jmap:查看堆内存
jmap -heap <pid> # 查看堆配置
jmap -histo <pid> # 查看对象统计
jmap -dump:format=b,file=heap.hprof <pid> # dump堆
# 4. jstack:查看线程栈
jstack <pid> # 查看线程栈
jstack <pid> > thread.txt # 导出到文件
# 5. jinfo:查看JVM参数
jinfo -flags <pid> # 查看所有参数
jinfo -flag MaxHeapSize <pid> # 查看指定参数
# 6. jconsole:图形化监控工具
jconsole
# 7. VisualVM:可视化工具
jvisualvm
四、实战案例
4.1 内存泄漏排查
java
// 内存泄漏示例
public class MemoryLeakDemo {
private static List<Object> list = new ArrayList<>();
public void addObject() {
// 不断添加对象,但从不清理
list.add(new byte[1024 * 1024]);
}
public static void main(String[] args) throws Exception {
MemoryLeakDemo demo = new MemoryLeakDemo();
while (true) {
demo.addObject();
Thread.sleep(100);
}
}
}
// 排查步骤:
// 1. jps查看进程ID
// 2. jmap -dump:format=b,file=heap.hprof <pid>
// 3. 使用MAT工具分析heap.hprof
// 4. 查找占用内存最大的对象
// 5. 分析对象引用链,找到泄漏点
4.2 CPU占用过高排查
java
// CPU占用过高示例
public class HighCPUDemo {
public static void main(String[] args) {
// 创建多个死循环线程
for (int i = 0; i < 10; i++) {
new Thread(() -> {
while (true) {
// 空循环,占用CPU
}
}, "high-cpu-thread-" + i).start();
}
}
}
// 排查步骤:
// 1. top命令查看CPU占用高的进程
// 2. top -Hp <pid> 查看该进程下的线程
// 3. printf "%x\n" <线程ID> 转换为16进制
// 4. jstack <pid> | grep <16进制线程ID> -A 20
// 5. 分析线程栈,找到问题代码
4.3 GC调优实战
java
// 电商系统GC调优
public class ECommerceGCTuning {
/**
* 场景:电商秒杀系统
* 问题:Full GC频繁,导致系统卡顿
*
* 原始配置:
* -Xms2g -Xmx2g
* -XX:+UseParallelGC
*
* 问题分析:
* 1. jstat -gcutil <pid> 1000
* 发现Full GC每分钟触发一次
* 2. jmap -histo <pid>
* 发现大量临时对象
*
* 优化方案:
* 1. 增大堆内存
* 2. 使用G1收集器
* 3. 调整新生代比例
*
* 优化后配置:
* -Xms4g -Xmx4g
* -Xmn1g
* -XX:+UseG1GC
* -XX:MaxGCPauseMillis=200
* -XX:G1HeapRegionSize=16m
*
* 优化效果:
* Full GC频率:1次/分钟 -> 1次/小时
* 平均响应时间:500ms -> 100ms
* 吞吐量提升:50%
*/
public static void main(String[] args) {
// 模拟秒杀场景
ExecutorService executor = Executors.newFixedThreadPool(100);
for (int i = 0; i < 10000; i++) {
executor.submit(() -> {
// 创建订单对象
Order order = new Order();
order.setOrderNo(UUID.randomUUID().toString());
order.setAmount(Math.random() * 1000);
// 处理订单
processOrder(order);
});
}
executor.shutdown();
}
private static void processOrder(Order order) {
// 订单处理逻辑
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
五、类加载机制
5.1 类加载过程
java
/**
* 类加载过程:
* 1. 加载(Loading):读取class文件
* 2. 验证(Verification):验证字节码
* 3. 准备(Preparation):分配内存,设置默认值
* 4. 解析(Resolution):符号引用转为直接引用
* 5. 初始化(Initialization):执行<clinit>方法
*/
public class ClassLoadDemo {
// 准备阶段:staticVar = 0
// 初始化阶段:staticVar = 100
private static int staticVar = 100;
static {
System.out.println("静态代码块执行");
staticVar = 200;
}
public ClassLoadDemo() {
System.out.println("构造方法执行");
}
public static void main(String[] args) {
System.out.println("main方法执行");
ClassLoadDemo demo = new ClassLoadDemo();
System.out.println("staticVar = " + staticVar);
}
}
// 输出:
// 静态代码块执行
// main方法执行
// 构造方法执行
// staticVar = 200
5.2 类加载器
java
/**
* 类加载器层次:
* 1. 启动类加载器(Bootstrap ClassLoader)
* 加载:jre/lib/rt.jar
*
* 2. 扩展类加载器(Extension ClassLoader)
* 加载:jre/lib/ext/*.jar
*
* 3. 应用类加载器(Application ClassLoader)
* 加载:classpath下的类
*
* 4. 自定义类加载器
*/
public class ClassLoaderDemo {
public static void main(String[] args) {
// 查看类加载器
ClassLoader classLoader = ClassLoaderDemo.class.getClassLoader();
System.out.println("当前类加载器:" + classLoader);
System.out.println("父类加载器:" + classLoader.getParent());
System.out.println("爷类加载器:" + classLoader.getParent().getParent());
// 输出:
// 当前类加载器:sun.misc.Launcher$AppClassLoader
// 父类加载器:sun.misc.Launcher$ExtClassLoader
// 爷类加载器:null(Bootstrap ClassLoader用C++实现)
}
}
// 自定义类加载器
public class MyClassLoader extends ClassLoader {
private String classPath;
public MyClassLoader(String classPath) {
this.classPath = classPath;
}
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
try {
byte[] classData = loadClassData(name);
return defineClass(name, classData, 0, classData.length);
} catch (IOException e) {
throw new ClassNotFoundException(name);
}
}
private byte[] loadClassData(String name) throws IOException {
String fileName = classPath + "/" + name.replace('.', '/') + ".class";
FileInputStream fis = new FileInputStream(fileName);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
int len;
byte[] buffer = new byte[1024];
while ((len = fis.read(buffer)) != -1) {
baos.write(buffer, 0, len);
}
fis.close();
return baos.toByteArray();
}
}
六、总结
JVM核心知识点:
- 内存结构 - 堆、栈、方法区
- 垃圾回收 - 算法、收集器、调优
- 性能调优 - 参数配置、问题排查
- 类加载 - 加载过程、类加载器
最佳实践:
- 合理设置堆内存大小
- 选择合适的垃圾收集器
- 开启GC日志,定期分析
- 使用性能分析工具
- OOM时自动dump堆
相关资源
💡 小贴士: JVM调优需要根据实际场景,不要盲目调整参数!
关注我,获取更多JVM干货! ☕