专栏原创出处:github-源笔记文件 (opens new window) ,github-源码 (opens new window),欢迎 Star,转载请附上原文出处链接和本声明。
Java JVM-虚拟机专栏系列笔记,系统性学习可访问个人复盘笔记-技术博客 Java JVM-虚拟机 (opens new window)
# 一、前言
熟悉了虚拟机的「内存数据区域」后,大多数的理解力提留在概念层面,找一段代码细细分析分配过程突然会感觉力不从心。
公网上流出许多错误的概念,比如 基础类型分配在栈上,对象分配在堆上!
这个错误的概念,虽然不足以影响我们编程,但是在虚拟机层面的深入学习会有一定误区。
# 代码级别内存分配总结
java 文件编译为 class 文件后,class 文件中除了有类的版本、字段、方法、接口等描述信息外,还有一项信息是常量池表(Constant Pool Table),用于存放编译期生成的各种字面量与符号引用,这部分内容将在类加载后存放到方法区的运行时常量池中。
术语说明: Memory m = new Memory();
- m 为「实例引用」
- m 对应的对象数据为「实例数据」
针对 JDK 8 细化:
class 文件类的版本、字段、方法、接口等描述信息、常量池表->方法区(本地内存)
字符串常量池->方法区(堆)
类静态成员-基础数据(常量)->方法区
类静态成员-实例引用->方法区
类静态成员-实例数据->堆
类非静态成员-基础数据->堆
类非静态成员-实例引用->堆
类非静态成员-实例数据->堆
方法内部-基础数据->栈帧
方法内部-实例引用->栈帧
方法内部-实例数据->堆(逃逸分析,直接将实例数据分配在栈帧上)
# 逃逸分析
public class EscapeAnalysis {
private int age;
public EscapeAnalysis(int age) {
this.age = age;
}
public int escape() {
// 优化为:age 直接在栈上分配,return age + 8;
return new EscapeAnalysis(10).age + 8;
}
}
# 写一段简单的实战代码
public class MemoryAllocation {
/* 类静态成员变量 */
private static final int DEFAULT_A = 2147483647;
private static final String DEFAULT_B = "static-variable";
private static final byte[] DEFAULT_BYTE = new byte[100 * 1024];
private static final MemoryAllocation MEMORY_ALLOCATION = new MemoryAllocation();
/* 类非静态成员变量 */
private int a;
private String b;
private byte[] bytes;
public MemoryAllocation() {}
public MemoryAllocation(int a, String b, byte[] bytes) {
this.a = a;
this.b = b;
this.bytes = bytes;
}
/* 类静态成员方法 */
public static MemoryAllocation defaultNoneInstance() {
return MEMORY_ALLOCATION;
}
/* 类静态成员方法 */
public static MemoryAllocation defaultInstance() {
return new MemoryAllocation(DEFAULT_A, DEFAULT_B, DEFAULT_BYTE);
}
}
声明术语:Java 中除了基础数据类型后,其余的都看做是对象类型(包括数组,String)
下面的这些数据分配在哪儿?
类静态成员变量-基础数据类型(DEFAULT_A、DEFAULT_B)
类静态成员变量-对象数据类型(DEFAULT_BYTE、MEMORY_ALLOCATION)
类静态成员方法-(defaultNoneInstance、defaultInstance)
类非静态成员变量-基础数据类型(a、b)
类非静态成员变量-对象数据类型(byte)
类静态成员方法-(构造方法)
我们使用 javap -c -v MemoryAllocation.class
反汇编成字节码,保留关键信息逐步分析
# 运行时常量池(Constant pool)分析
运行时常量池是方法区的一部分,也就是 JDK 8 后的元空间(本地内存)的一部分。
public class MemoryAllocation
Constant pool:
#1 = Methodref #13.#39 // java/lang/Object."<init>":()V
#2 = Fieldref #6.#40 // MemoryAllocation.a:I
#3 = Fieldref #6.#41 // MemoryAllocation.b:Ljava/lang/String;
#4 = Fieldref #6.#42 // MemoryAllocation.bytes:[B
#5 = Fieldref #6.#43 // MemoryAllocation.MEMORY_ALLOCATION:LMemoryAllocation;
#6 = Class #44 // MemoryAllocation
#7 = Integer 2147483647
#8 = String #45 // static-variable
#9 = Fieldref #6.#46 // MemoryAllocation.DEFAULT_BYTE:[B
#10 = Methodref #6.#47 // MemoryAllocation."<init>":(ILjava/lang/String;[B)V
#11 = Integer 102400
#12 = Methodref #6.#39 // MemoryAllocation."<init>":()V
#13 = Class #48 // java/lang/Object
#14 = Utf8 DEFAULT_A
#15 = Utf8 I
#16 = Utf8 ConstantValue
#17 = Utf8 DEFAULT_B
#18 = Utf8 Ljava/lang/String;
#19 = Utf8 DEFAULT_BYTE
#20 = Utf8 [B
#21 = Utf8 MEMORY_ALLOCATION
#22 = Utf8 LMemoryAllocation;
#23 = Utf8 a
#24 = Utf8 b
#25 = Utf8 bytes
#26 = Utf8 <init>
#27 = Utf8 ()V
#28 = Utf8 Code
#29 = Utf8 LineNumberTable
#30 = Utf8 LocalVariableTable
#31 = Utf8 this
#32 = Utf8 (ILjava/lang/String;[B)V
#33 = Utf8 defaultNoneInstance
#34 = Utf8 ()LMemoryAllocation;
#35 = Utf8 defaultInstance
#36 = Utf8 <clinit>
#37 = Utf8 SourceFile
#38 = Utf8 MemoryAllocation.java
#39 = NameAndType #26:#27 // "<init>":()V
#40 = NameAndType #23:#15 // a:I
#41 = NameAndType #24:#18 // b:Ljava/lang/String;
#42 = NameAndType #25:#20 // bytes:[B
#43 = NameAndType #21:#22 // MEMORY_ALLOCATION:LMemoryAllocation;
#44 = Utf8 MemoryAllocation
#45 = Utf8 static-variable
#46 = NameAndType #19:#20 // DEFAULT_BYTE:[B
#47 = NameAndType #26:#32 // "<init>":(ILjava/lang/String;[B)V
#48 = Utf8 java/lang/Object
静态成员变量分析:
#7 = Integer 2147483647
为 DEFAULT_A 的常量#8 = String #45 // static-variable
为 DEFAULT_B 的常量#9 = Fieldref #6.#46 // MemoryAllocation.DEFAULT_BYTE:[B
DEFAULT_BYTE 字段对应符号引用指向了类.变量名符号.类型符号
非静态成员变量(也叫类实例变量)分析:
#2 = Fieldref #6.#40 // MemoryAllocation.a:I
引用符号#3 = Fieldref #6.#41 // MemoryAllocation.b:Ljava/lang/String;
引用符号#4 = Fieldref #6.#42 // MemoryAllocation.bytes:[B
引用符号
静态成员方法 defaultInstance 分析:
/* 类静态成员方法 */
public static MemoryAllocation defaultInstance() {
return new MemoryAllocation(DEFAULT_A, DEFAULT_B, DEFAULT_BYTE);
}
Code:
stack=5, locals=0, args_size=0
0: new #6 // 创建一个对象, 并将其引用引用值压入栈顶
3: dup // 复制栈顶数值并将复制值压入栈顶
4: ldc #7 // 将 2147483647 常量值从常量池中推送至栈顶
6: ldc #8 // 将 static-variable 常量值从常量池中推送至栈顶
8: getstatic #9 // Field DEFAULT_BYTE:[B 获取指定类的静态域 DEFAULT_BYTE , 并将其压入栈顶
11: invokespecial #10 // Method "<init>":(ILjava/lang/String;[B)V 构造方法
14: areturn // 返回对象引用
LineNumberTable:
line 39: 0
{
public MemoryAllocation();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
LineNumberTable:
line 23: 0
line 24: 4
LocalVariableTable:
Start Length Slot Name Signature
0 5 0 this LMemoryAllocation;
public MemoryAllocation(int, java.lang.String, byte[]);
descriptor: (ILjava/lang/String;[B)V
flags: ACC_PUBLIC
Code:
stack=2, locals=4, args_size=4
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: aload_0
5: iload_1
6: putfield #2 // Field a:I
9: aload_0
10: aload_2
11: putfield #3 // Field b:Ljava/lang/String;
14: aload_0
15: aload_3
16: putfield #4 // Field bytes:[B
19: return
LineNumberTable:
line 26: 0
line 27: 4
line 28: 9
line 29: 14
line 30: 19
LocalVariableTable:
Start Length Slot Name Signature
0 20 0 this LMemoryAllocation;
0 20 1 a I
0 20 2 b Ljava/lang/String;
0 20 3 bytes [B
public static MemoryAllocation defaultNoneInstance();
descriptor: ()LMemoryAllocation;
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=1, locals=0, args_size=0
0: getstatic #5 // Field MEMORY_ALLOCATION:LMemoryAllocation;
3: areturn
LineNumberTable:
line 34: 0
public static MemoryAllocation defaultInstance();
descriptor: ()LMemoryAllocation;
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=5, locals=0, args_size=0
0: new #6 // class MemoryAllocation
3: dup
4: ldc #7 // int 2147483647
6: ldc #8 // String static-variable
8: getstatic #9 // Field DEFAULT_BYTE:[B
11: invokespecial #10 // Method "<init>":(ILjava/lang/String;[B)V
14: areturn
LineNumberTable:
line 39: 0
static {};
descriptor: ()V
flags: ACC_STATIC
Code:
stack=2, locals=0, args_size=0
0: ldc #11 // int 102400
2: newarray byte
4: putstatic #9 // Field DEFAULT_BYTE:[B
7: new #6 // class MemoryAllocation
10: dup
11: invokespecial #12 // Method "<init>":()V
14: putstatic #5 // Field MEMORY_ALLOCATION:LMemoryAllocation;
17: return
LineNumberTable:
line 15: 0
line 16: 7
}
SourceFile: "MemoryAllocation.java"
# 参考
- 字节码翻译字典-字节码指令集 (opens new window)