JVM深度解析:从原理到调优
> JVM(Java虚拟机)是Java技术栈的心脏。深入理解JVM,不仅能让你写出更高效的代码,更能在性能瓶颈出现时快速定位和解决问题。
---
## 前言
JVM(Java Virtual Machine)是Java平台的核心组成部分,它负责将Java字节码转换为特定平台上的机器码执行。JVM的设计使得Java实现了"一次编写,到处运行"的理念。
本文将全面解析JVM的内部机制、内存模型、垃圾回收、类加载机制以及性能调优实战。
---
## 一、JVM整体架构
### 1.1 JVM核心组件
```
- Class Files(类文件)
↓
- Class Loader(类加载器)
↓
- Runtime Data Areas
--- Method Area(方法区)
--- Heap(堆)
--- Stack(栈)
--- PC Register(程序计数器
--- Native Method Stack(本地方法栈)
↓
- Execution Engine
--- Interpreter(解释器)
--- JIT Compiler(即时编译器)
--- GC(垃圾回收器)
↓
- Native Interface
--- JNI(Java Native Interface)
↓
- Native Libraries
```
### 1.2 JVM内存区域详解
#### 方法区(Method Area)
**作用**:存储类信息、常量池、静态变量
```java
public class MethodAreaDemo {
// 类信息:类名、方法名、访问修饰符等
public static final String CONSTANT = "Constant Value"; // 常量
// 静态变量存储在方法区
private static int staticVariable = 0;
// 类信息
public void instanceMethod() {
System.out.println("Instance method");
}
public static void staticMethod() {
System.out.println("Static method");
}
}
```
**特点**:
- 线程共享
- 存储类级别的数据
- 在Java 8中称为"元空间(Metaspace)",使用本地内存
- 在Java 8之前称为"永久代(PermGen)",在堆中
总结而言:
“方法区是 JVM 规范中的一块逻辑内存,用于存储类结构信息、常量、静态变量等。在 Java 8 之前,它由永久代实现,位于堆中;在 Java 8 及之后,它由元空间实现,移出堆并改用本地内存,主要为了解决永久代内存溢出的问题。”
**JVM参数**:
```bash
# Java 8之前
-XX:PermSize=128m
-XX:MaxPermSize=256m
# Java 8及以后
-XX:MetaspaceSize=256m
-XX:MaxMetaspaceSize=512m
```
#### 堆(Heap)
**作用**:存储对象实例和数组
```java
public class HeapDemo {
public static void main(String[] args) {
// 对象实例存储在堆中
String str = new String("Hello"); // 对象在堆中
int[] array = new int[1000]; // 数组在堆中
// 对象引用存储在栈中,指向堆中的对象
Object ref = new Object();
// 大对象直接分配在堆的Old Generation
byte[] largeArray = new byte[10 * 1024 * 1024]; // 10MB
}
}
```
**堆内存结构**:
```
Java8之前
┌─────────────────────────────────────────────────┐
│ 堆内存 (Heap) │
│ ┌───────────────────────────────────────────┐ │
│ │ Young Generation │ │
│ │ ┌──────────┐ ┌─────┐ ┌─────┐ │ │
│ │ │ Eden │ │ S0 │ │ S1 │ │ │
│ │ │ (8) │ │ (1) │ │ (1) │ │ │
│ │ └──────────┘ └─────┘ └─────┘ │ │
│ ├───────────────────────────────────────────┤ │
│ │ Old Generation │ │
│ │ (长期存活对象、大对象) │ │
│ ├───────────────────────────────────────────┤ │
│ │ PermGen (永久代) │ │
│ │ - 类的元数据(类名、方法、字段) │ │
│ │ - 运行时常量池(字符串常量池在 Java 7 移到堆) │ │
│ │ - 静态变量 │ │
│ │ - JIT 编译后的代码 │ │
│ └───────────────────────────────────────────┘ │
└─────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────┐
│ 本地内存 (Native Memory) │
│ - 线程栈 │
│ - DirectBuffer 缓冲区 │
│ - JNI 代码 │
└─────────────────────────────────────────────────┘
Java8及其之后
┌─────────────────────────────────────┐
│ 堆内存 (Heap) │
│ ┌───────────────────────────────┐ │
│ │ Young Generation │ │
│ │ ┌─────────────────────────┐ │ │
│ │ │ Eden (8/10) │ │ │
│ │ └─────────────────────────┘ │ │
│ │ ┌─────────┬─────────────┐ │ │
│ │ │ S0 (1/10)│ S1 (1/10) │ │ │
│ │ └─────────┴─────────────┘ │ │
│ ├───────────────────────────────┤ │
│ │ Old Generation │ │
│ │ (大对象、长期存活对象) │ │
│ └───────────────────────────────┘ │
└─────────────────────────────────────┘
┌─────────────────────────────────────┐
│ 本地内存 (Native Memory) │
│ ┌───────────────────────────────┐ │
│ │ Metaspace(元空间) │ │
│ │ (类信息、常量池、方法数据) │ │
│ └───────────────────────────────┘ │
│ ┌───────────────────────────────┐ │
│ │ 其他(线程栈、DirectBuffer等) │ │
│ └───────────────────────────────┘ │
└─────────────────────────────────────┘
Java 7 中永久代常见的问题
---[[
// 典型场景:动态生成类导致 PermGen OOM
for (int i = 0; i < 100000; i++) {
// 使用 CGLib、JSP、反射等动态生成类
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(MyClass.class);
enhancer.setCallback(new MethodInterceptor() {...});
MyClass proxy = (MyClass) enhancer.create();
}
// 结果:java.lang.OutOfMemoryError: PermGen space
---]]
为什么 Java 8 要把 PermGen 移到堆外?
---[[
问题 说明
------------------------------
PermGen 大小难调 太小容易 OOM,太大会浪费堆内存
GC 效率低 PermGen 中的类信息回收条件苛刻,Full GC 才能回收
字符串常量池冲突 Java 7 把字符串常量池移到堆,但 PermGen 仍存在
内存碎片 永久代在堆中容易产生碎片
---]]
```
**JVM参数**:
```bash
# 初始堆大小
-Xms512m
# 最大堆大小
-Xmx2g
# 新生代比例
-XX:NewRatio=2 # 新生代:老年代 = 1:2
# Eden区与Survivor区比例
-XX:SurvivorRatio=8 # Eden:Survivor = 8:1:1
```
#### 虚拟机栈(Java Stack)
**作用**:存储方法调用的栈帧
```java
public class StackDemo {
public static void main(String[] args) {
methodA(); // 创建栈帧
}
public static void methodA() {
int a = 10; // 局部变量表
methodB(); // 创建新栈帧
System.out.println(a);
}
public static void methodB() {
int b = 20; // 局部变量表
methodC(); // 创建新栈帧
}
public static void methodC() {
int c = 30; // 局部变量表
// 方法结束,栈帧弹出
}
}
```
**栈帧结构**:
```
┌─────────────────────────┐
│ 栈帧(Frame) │
│ ┌───────────────────┐ │
│ │ 局部变量表 │ │
│ │ Operand Stack │ │
│ │ 动态链接 │ │
│ │ 返回地址 │ │
│ └───────────────────┘ │
└─────────────────────────┘
```
**JVM参数**:
```bash
# 每个线程栈大小
-Xss1m
# 最大栈深度
-XX:MaxJavaStackTraceDepth=1000
```
**StackOverflowError示例**:
```java
public class StackOverflowDemo {
private static int depth = 0;
public static void main(String[] args) {
deepRecursion();
}
public static void deepRecursion() {
depth++;
deepRecursion(); // 无限递归,栈溢出
}
}
// 运行结果:
// Exception in thread "main" java.lang.StackOverflowError
```
#### 程序计数器(PC Register)
**作用**:存储当前线程执行的字节码指令地址
```java
public class PCRegisterDemo {
public static void main(String[] args) {
int a = 10;
int b = 20;
int c = a + b; // PC寄存器记录执行位置
if (c > 20) {
System.out.println("Greater");
} else {
System.out.println("Less or equal");
}
}
}
```
**特点**:
- 线程私有
- 很小(可能为null,执行native方法时)
- 记录字节码执行位置
#### 本地方法栈(Native Method Stack)
**作用**:为native方法服务
```java
public class NativeMethodDemo {
// native方法调用C/C++代码
public native void nativeMethod();
static {
System.loadLibrary("nativeLibrary");
}
public static void main(String[] args) {
NativeMethodDemo demo = new NativeMethodDemo();
demo.nativeMethod();
}
}
```
---
## 二、类加载机制
### 2.1 类加载过程
```
┌──────────┐ ┌───────────┐ ┌────────────┐ ┌──────────┐
│ 加载 │ → │ 验证 │ → │ 准备 │ → │ 解析 │
│Loading │ │Verifying │ │ Preparing │ │ Resolving│
└──────────┘ └───────────┘ └────────────┘ └──────────┘
│
▼
┌────────────┐
│ 初始化 │
│Initializing│
└────────────┘
```
#### 1. 加载(Loading)
```java
// 加载阶段:通过类全限定名获取二进制字节流
public class LoadingDemo {
public static void main(String[] args) throws Exception {
// 方式1:Class.forName()
Class> clazz1 = Class.forName("java.lang.String");
// 方式2:类字面量
Class> clazz2 = String.class;
// 方式3:对象.getClass()
String str = "Hello";
Class> clazz3 = str.getClass();
// 方式4:类加载器加载
Class> clazz4 = ClassLoader.getSystemClassLoader()
.loadClass("java.lang.String");
}
}
// 自定义类加载器
public class MyClassLoader extends ClassLoader {
@Override
protected Class> findClass(String name) throws ClassNotFoundException {
try {
byte[] classBytes = loadClassBytes(name);
return defineClass(name, classBytes, 0, classBytes.length);
} catch (IOException e) {
throw new ClassNotFoundException(name);
}
}
private byte[] loadClassBytes(String name) throws IOException {
// 从文件系统或网络加载字节码
String path = name.replace('.', '/') + ".class";
try (InputStream is = getClass().getClassLoader().getResourceAsStream(path)) {
if (is == null) {
throw new IOException("Class not found: " + path);
}
ByteArrayOutputStream baos = new ByteArrayOutputStream();
byte[] buffer = new byte[1024];
int len;
while ((len = is.read(buffer)) != -1) {
baos.write(buffer, 0, len);
}
return baos.toByteArray();
}
}
}
```
#### 2. 验证(Verification)
```java
// 验证阶段:确保字节码文件的正确性
public class VerificationDemo {
// JVM会验证:
// 1. 文件格式验证:魔数、版本号等
// 2. 字节码验证:操作数栈类型、局部变量类型等
// 3. 符号引用验证:类、字段、方法的引用是否正确
public static void main(String[] args) {
int x = 10;
String s = (String) (Object) x; // 编译时错误,验证不通过
// Type mismatch: cannot convert from int to String
}
}
```
#### 3. 准备(Preparation)
```java
// 准备阶段:为静态变量分配内存并设置初始值
public class PreparationDemo {
// 这些变量在准备阶段分配内存并初始化
public static int value = 123; // 初始化为0(不是123)
public static final int CONSTANT = 456; // 初始化为456(常量)
public static String name = "Java"; // 初始化为null(不是"Java")
static {
// 静态代码块在初始化阶段执行
value = 123;
name = "Java";
}
}
```
#### 4. 解析(Resolution)
```java
// 解析阶段:将常量池中的符号引用替换为直接引用
public class ResolutionDemo {
public static void main(String[] args) {
String str = "Hello"; // String常量池引用解析
System.out.println(str.length()); // 方法引用解析
}
}
class Parent {
static {
System.out.println("Parent static block");
}
}
class Child extends Parent {
static {
System.out.println("Child static block");
}
}
// 调用触发类加载和初始化
class ResolutionDemo2 {
public static void main(String[] args) {
// 触发Parent类加载和初始化
System.out.println(Parent.class);
// 输出:
// Parent static block
// class Parent
// 不触发Child类加载
System.out.println(Child.class);
// 输出:
// class Child
}
}
```
#### 5. 初始化(Initialization)
```java
// 初始化阶段:执行方法
public class InitializationDemo {
// 静态变量赋值和静态代码块按顺序执行
private static int value = initValue();
private static int initValue() {
System.out.println("Initializing value");
return 10;
}
static {
System.out.println("Static block");
value = 20;
}
public static void main(String[] args) {
System.out.println("Main method");
System.out.println("Value: " + value);
// 输出顺序:
// Initializing value
// Static block
// Main method
// Value: 20
}
}
// 类初始化时机
class ClassInitTiming {
static class A {
static final int CONSTANT = 100; // 不会触发初始化
static {
System.out.println("A initialized");
}
}
static class B {
static int value = 100; // 会触发初始化
static {
System.out.println("B initialized");
}
}
public static void main(String[] args) {
System.out.println(A.CONSTANT); // 不触发A的初始化
System.out.println(B.value); // 触发B的初始化
}
}
```
### 2.2 类加载器
#### 双亲委派模型
```
Bootstrap ClassLoader(启动类加载器)
↑
Extension ClassLoader(扩展类加载器)
↑
Application ClassLoader(应用类加载器)
↑
Custom ClassLoader(自定义类加载器)
```
```java
// 双亲委派模型示例
public class ClassLoaderDemo {
public static void main(String[] args) {
// 获取系统类加载器
ClassLoader systemLoader = ClassLoader.getSystemClassLoader();
System.out.println("System ClassLoader: " + systemLoader);
// 获取扩展类加载器(Java 9后称为平台类加载器)
ClassLoader parentLoader = systemLoader.getParent();
System.out.println("Parent ClassLoader: " + parentLoader);
// 启动类加载器(C++实现,返回null)
ClassLoader bootstrapLoader = parentLoader.getParent();
System.out.println("Bootstrap ClassLoader: " + bootstrapLoader);
// 获取String类的加载器(启动类加载器)
ClassLoader stringLoader = String.class.getClassLoader();
System.out.println("String ClassLoader: " + stringLoader); // null
// 获取自定义类的加载器(应用类加载器)
ClassLoader myLoader = ClassLoaderDemo.class.getClassLoader();
System.out.println("My ClassLoader: " + myLoader);
}
}
// 自定义类加载器(破坏双亲委派)
public class CustomClassLoader extends ClassLoader {
private String classPath;
public CustomClassLoader(String classPath) {
this.classPath = classPath;
}
@Override
protected Class> loadClass(String name, boolean resolve)
throws ClassNotFoundException {
// 破坏双亲委派:先尝试自己加载
synchronized (getClassLoadingLock(name)) {
Class> c = findLoadedClass(name);
if (c == null) {
try {
byte[] classData = loadClassData(name);
if (classData != null) {
c = defineClass(name, classData, 0, classData.length);
}
} catch (IOException e) {
// ignore
}
}
if (c == null) {
// 自己加载失败,委托给父加载器
c = super.loadClass(name, resolve);
}
if (resolve) {
resolveClass(c);
}
return c;
}
}
private byte[] loadClassData(String name) throws IOException {
String path = classPath + name.replace('.', '/') + ".class";
try (InputStream is = new FileInputStream(path)) {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
byte[] buffer = new byte[1024];
int len;
while ((len = is.read(buffer)) != -1) {
baos.write(buffer, 0, len);
}
return baos.toByteArray();
}
}
}
```
### 2.3 类加载器的实战应用
```java
// 热部署示例
public class HotSwapDemo {
public static void main(String[] args) throws Exception {
String className = "com.example.HotSwapClass";
CustomClassLoader loader = new CustomClassLoader("classes/");
// 第一次加载
Class> clazz1 = loader.loadClass(className);
Object obj1 = clazz1.getDeclaredConstructor().newInstance();
Method method1 = clazz1.getMethod("execute");
method1.invoke(obj1);
// 修改class文件后重新加载
Thread.sleep(5000); // 等待文件修改
CustomClassLoader loader2 = new CustomClassLoader("classes/");
Class> clazz2 = loader2.loadClass(className);
Object obj2 = clazz2.getDeclaredConstructor().newInstance();
Method method2 = clazz2.getMethod("execute");
method2.invoke(obj2);
// 检查是否是同一个类
System.out.println("Same class: " + (clazz1 == clazz2)); // false
}
}
// OSGi模块化示例(简化版)
public class OSGiDemo {
private Map bundles = new ConcurrentHashMap<>();
public void installBundle(String bundleId, String classPath) {
BundleClassLoader loader = new BundleClassLoader(classPath);
Bundle bundle = new Bundle(bundleId, loader);
bundles.put(bundleId, bundle);
bundle.start();
}
public void startBundle(String bundleId) {
Bundle bundle = bundles.get(bundleId);
if (bundle != null) {
bundle.start();
}
}
public void stopBundle(String bundleId) {
Bundle bundle = bundles.get(bundleId);
if (bundle != null) {
bundle.stop();
}
}
class Bundle {
private String id;
private BundleClassLoader loader;
private boolean active;
public Bundle(String id, BundleClassLoader loader) {
this.id = id;
this.loader = loader;
}
public void start() {
active = true;
System.out.println("Bundle " + id + " started");
}
public void stop() {
active = false;
System.out.println("Bundle " + id + " stopped");
}
}
class BundleClassLoader extends ClassLoader {
private String classPath;
public BundleClassLoader(String classPath) {
this.classPath = classPath;
}
@Override
protected Class> findClass(String name) throws ClassNotFoundException {
// 只加载自己的类
if (!name.startsWith("com.example.osgi.")) {
throw new ClassNotFoundException(name);
}
try {
byte[] bytes = loadClassBytes(name);
return defineClass(name, bytes, 0, bytes.length);
} catch (IOException e) {
throw new ClassNotFoundException(name, e);
}
}
private byte[] loadClassBytes(String name) throws IOException {
String path = classPath + name.replace('.', '/') + ".class";
try (InputStream is = new FileInputStream(path)) {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
byte[] buffer = new byte[1024];
int len;
while ((len = is.read(buffer)) != -1) {
baos.write(buffer, 0, len);
}
return baos.toByteArray();
}
}
}
}
```
---
## 三、垃圾回收机制
### 3.1 垃圾对象识别
#### 引用计数法(Reference Counting)
```java
// 引用计数法(JVM不采用,但用于理解)
public class ReferenceCountingDemo {
private Object ref;
public void setRef(Object ref) {
this.ref = ref;
}
public static void main(String[] args) {
Object obj = new Object();
ReferenceCountingDemo demo1 = new ReferenceCountingDemo();
ReferenceCountingDemo demo2 = new ReferenceCountingDemo();
demo1.setRef(obj); // 引用计数 = 1
demo2.setRef(obj); // 引用计数 = 2
demo1.setRef(null); // 引用计数 = 1
demo2.setRef(null); // 引用计数 = 0,可以回收
}
// 循环引用问题
public static void circularReference() {
class Node {
Node next;
}
Node node1 = new Node();
Node node2 = new Node();
node1.next = node2;
node2.next = node1;
// 引用计数法无法回收node1和node2
// 但可达性分析可以正确识别
}
}
```
#### 可达性分析(Reachability Analysis)
```java
// 可达性分析(JVM实际使用的方法)
public class ReachabilityAnalysisDemo {
// GC Roots包括:
// 1. 虚拟机栈中引用的对象
// 2. 方法区中类静态属性引用的对象
// 3. 方法区中常量引用的对象
// 4. 本地方法栈中JNI引用的对象
public static Object staticRef; // 静态变量 - GC Root
public static final Object CONSTANT_REF = new Object(); // 常量 - GC Root
public void method() {
Object localVar = new Object(); // 局部变量 - GC Root(在栈中)
method2(localVar);
}
public void method2(Object param) {
// param参数引用的对象从局部变量可达
System.out.println(param);
}
// 垃圾对象示例
public void createGarbage() {
Object obj1 = new Object();
Object obj2 = new Object();
Object obj3 = new Object();
obj1 = null; // obj1指向的对象变成垃圾
obj2 = obj3; // obj2指向的对象变成垃圾
}
}
```
### 3.2 引用类型
```java
// 四种引用类型
import java.lang.ref.*;
public class ReferenceTypesDemo {
// 强引用(Strong Reference)
public static void strongReference() {
Object strongRef = new Object();
// 对象不会被回收,除非strongRef被显式设置为null
strongRef = null; // 现在对象可以被回收
}
// 软引用(Soft Reference)
public static void softReference() {
Object obj = new Object();
SoftReference