📍 方法区(Method Area)整体结构
方法区 (线程共享)
🏗️ 类信息存储
- 类的元数据:类名、父类、接口信息
- 字段信息:字段名、类型、修饰符
- 方法信息:方法名、参数、返回类型、字节码
- 访问修饰符:public、private、protected等
🔄 运行时常量池
作用:存储编译期确定的字面量和符号引用,运行时进行地址解析
| 索引 | 类型 | 符号引用(编译期) | 真实地址(运行期) | 说明 |
|---|---|---|---|---|
| #1 | 类引用 | java/lang/Object | 0x00007f8a4c001020 | Object类的内存地址 |
| #2 | 方法引用 | println:(Ljava/lang/String;)V | 0x00007f8a4c002150 | println方法地址 |
| #3 | 字符串字面量 | "Hello World" | 0x00007f8a4c003280 | 字符串对象地址 |
| #4 | 字段引用 | System.out:Ljava/io/PrintStream; | 0x00007f8a4c004390 | System.out字段地址 |
⚠️ 异常处理
OutOfMemoryError: MetaSpace
当方法区内存不足时抛出此异常,通常发生在动态加载大量类的场景
当方法区内存不足时抛出此异常,通常发生在动态加载大量类的场景
🔄 符号地址 → 真实地址 转换过程
编译期 - 符号引用
java/lang/String
println(Ljava/lang/String;)V
➡️
类加载时 - 地址解析
虚拟机查找并加载类
解析符号引用
符号引用 → 直接引用➡️
运行期 - 真实地址
0x00007f8a4c001020
0x00007f8a4c002150
为什么需要符号引用?
编译期无法确定类的实际内存位置,使用符号引用可以让程序在不同环境下正常运行。 只有在类加载时,JVM才知道类的确切内存位置,此时将符号引用解析为直接引用。
编译期无法确定类的实际内存位置,使用符号引用可以让程序在不同环境下正常运行。 只有在类加载时,JVM才知道类的确切内存位置,此时将符号引用解析为直接引用。
💻 Java反编译示例 (javap 命令)
/*=== Java反编译结果 (javap 命令输出) ===*/ // 使用命令: javap -verbose HelloWorld.class // 查看字节码: javap -c HelloWorld.class Compiled from "HelloWorld.java" public class HelloWorld { // 默认构造方法 public HelloWorld(); descriptor: ()V flags: ACC_PUBLIC Code: stack=1, locals=1, args_size=1 0: aload_0 // 加载this引用到操作数栈 1: invokespecial #1 // 调用Object构造方法,#1指向常量池 4: return // 方法返回 // main方法 - 程序入口 public static void main(java.lang.String[]); descriptor: ([Ljava/lang/String;)V flags: ACC_PUBLIC, ACC_STATIC Code: stack=2, locals=1, args_size=1 0: getstatic #2 // 获取System.out字段,#2指向常量池 3: ldc #3 // 加载字符串常量到栈,#3指向常量池 5: invokevirtual #4 // 调用println方法,#4指向常量池 8: return // 方法返回 // 运行时常量池详细信息 Constant pool: #1 = Methodref #6.#15 // java/lang/Object."<init>":()V #2 = Fieldref #16.#17 // java/lang/System.out:Ljava/io/PrintStream; #3 = String #18 // "Hello World" #4 = Methodref #19.#20 // java/io/PrintStream.println:(Ljava/lang/String;)V #5 = Class #21 // HelloWorld #6 = Class #22 // java/lang/Object #7 = Utf8 <init> #8 = Utf8 ()V ... }
🏗️ JVM内存区域分布
🔄 方法区
线程共享
• 类信息
• 运行时常量池
• 静态变量
📚 堆内存
线程共享
• 对象实例
• 数组
• 实例变量
📋 栈内存
线程私有
• 方法调用栈
• 局部变量
• 方法参数
🎯 程序计数器
线程私有
• 当前指令位置
• 线程执行状态
📝 关键概念总结
🎯 符号引用 vs 直接引用
- 符号引用:编译期使用的文本描述,如 "java/lang/String"
- 直接引用:运行期的真实内存地址,如 0x00007f8a4c001020
- 转换时机:类加载的解析阶段完成转换
- 转换原因:编译期无法确定类的最终内存位置
🔍 javap 命令详解
常用javap命令选项:
•
•
•
•
•
•
javap -verbose ClassName - 显示详细信息(包括常量池)•
javap -c ClassName - 显示字节码指令•
javap -p ClassName - 显示所有类和成员(包括私有)•
javap -s ClassName - 显示内部类型签名•
javap -l ClassName - 显示行号和局部变量表
提示:java -p 是用于设置模块路径的参数,不是查看字节码的命令。
查看字节码和常量池信息请使用
查看字节码和常量池信息请使用
javap 工具。