一、从房屋户型图理解JVM的内存划分原理与基本结构介绍
大白话:
这是JDK1.6的内存结构,JDK1.8以后方法区已经挪出来了,不属于JVM内存。
二、第一步:掌握程序计数器的功能与工作过程
mac@MacdeMBP ch2_class_loader % javap -v PC
警告: 二进制文件PC包含ch2_class_loader.PC
Classfile /Users/mac/IdeaProjects/OOM/OOM/out/production/OOM/ch2_class_loader/PC.class
Last modified 2023-12-29; size 491 bytes
MD5 checksum b0849816b5a32d4c8412bfd0452f93e2
Compiled from "PC.java"
public class ch2_class_loader.PC
minor version: 0
major version: 52
flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
#1 = Methodref #3.#21 // java/lang/Object."":()V
#2 = Class #22 // ch2_class_loader/PC
#3 = Class #23 // java/lang/Object
#4 = Utf8
#5 = Utf8 ()V
#6 = Utf8 Code
#7 = Utf8 LineNumberTable
#8 = Utf8 LocalVariableTable
#9 = Utf8 this
#10 = Utf8 Lch2_class_loader/PC;
#11 = Utf8 main
#12 = Utf8 ([Ljava/lang/String;)V
#13 = Utf8 args
#14 = Utf8 [Ljava/lang/String;
#15 = Utf8 i
#16 = Utf8 I
#17 = Utf8 j
#18 = Utf8 StackMapTable
#19 = Utf8 SourceFile
#20 = Utf8 PC.java
#21 = NameAndType #4:#5 // "":()V
#22 = Utf8 ch2_class_loader/PC
#23 = Utf8 java/lang/Object
{
public ch2_class_loader.PC();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: invokespecial #1 // Method java/lang/Object."":()V
4: return
LineNumberTable:
line 3: 0
LocalVariableTable:
Start Length Slot Name Signature
0 5 0 this Lch2_class_loader/PC;
public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=2, locals=4, args_size=1
0: bipush 10
2: istore_1
3: bipush 20
5: istore_2
6: iload_1
7: iload_2
8: if_icmple 18
11: iload_1
12: iload_2
13: iadd
14: istore_3
15: goto 22
18: iload_1
19: iload_2
20: isub
21: istore_3
22: return
LineNumberTable:
line 5: 0
line 6: 3
line 8: 6
line 9: 11
line 10: 15
line 11: 18
line 13: 22
LocalVariableTable:
Start Length Slot Name Signature
0 23 0 args [Ljava/lang/String;
3 20 1 i I
6 17 2 j I
StackMapTable: number_of_entries = 2
frame_type = 253 /* append */
offset_delta = 18
locals = [ int, int ]
frame_type = 3 /* same */
}
SourceFile: "PC.java"
mac@MacdeMBP ch2_class_loader %
三、第二步:掌握虚拟机栈的功能与故障实战新
大白话:
function() -> function1() -> function2() -> function3() -> function2() -> function1() -> function(), 结果必须等所有方法依次执行完,再依次返回结果,在这个过程中,每个方法的信息不能丢掉,保存在栈帧。
1.一个线程一次只能执行一个方法
2.先进后出
3.栈帧 - 存储当前方法的一些特征
大白话:
经常见到异常StackOverFlow,就是因为方法递归层次过深,或者方法内部创建的对象过多导致栈空间不足,就会发生栈溢出错误。
四、掌握虚拟机栈的内部结构
大白话:
1.局部变量表 - 保存方法中的变量;
2.操作数栈 - 执行方法运算;
3.动态链接 - 执行方法调用;
4.方法返回地址 - 返回值
局部变量表、操作数栈的大小在字节码生成的时候,就已经确定了,具体如下图:
大白话:
上图的
stack - 操作数栈的大小 单位:单位空间
locals - 本地变量的数量
Slot - 变量的大小,比如int - 32位(4个字节),long - 64位(8个字节)、short - 16位(2个字节)、byte - 8位(1个字节)、float - 32位(4个字节)
32位表示1个slot
参考:Java虚拟机(三)—— Java变量在虚拟机中的内存分配_java虚拟机内存分配-CSDN博客
警告: 二进制文件StackTest3包含ch2_class_loader.StackTest3
Classfile /Users/mac/IdeaProjects/OOM/OOM/out/production/OOM/ch2_class_loader/StackTest3.class
Last modified 2024-1-2; size 603 bytes
MD5 checksum e3eb2d1910da782f57543dfac3b9ba3e
Compiled from "StackTest3.java"
public class ch2_class_loader.StackTest3
minor version: 0
major version: 52
flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
#1 = Methodref #6.#28 // java/lang/Object."
#2 = Double 30.0d
#4 = Methodref #5.#29 // ch2_class_loader/StackTest3.test:()V
#5 = Class #30 // ch2_class_loader/StackTest3
#6 = Class #31 // java/lang/Object
#7 = Utf8
#8 = Utf8 ()V
#9 = Utf8 Code
#10 = Utf8 LineNumberTable
#11 = Utf8 LocalVariableTable
#12 = Utf8 this
#13 = Utf8 Lch2_class_loader/StackTest3;
#14 = Utf8 test
#15 = Utf8 a
#16 = Utf8 I
#17 = Utf8 b
#18 = Utf8 c
#19 = Utf8 d
#20 = Utf8 D
#21 = Utf8 e
#22 = Utf8 main
#23 = Utf8 ([Ljava/lang/String;)V
#24 = Utf8 args
#25 = Utf8 [Ljava/lang/String;
#26 = Utf8 SourceFile
#27 = Utf8 StackTest3.java
#28 = NameAndType #7:#8 // "
#29 = NameAndType #14:#8 // test:()V
#30 = Utf8 ch2_class_loader/StackTest3
#31 = Utf8 java/lang/Object
{
public ch2_class_loader.StackTest3();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: invokespecial #1 // Method java/lang/Object."
4: return
LineNumberTable:
line 3: 0
LocalVariableTable:
Start Length Slot Name Signature
0 5 0 this Lch2_class_loader/StackTest3;
public static void test();
descriptor: ()V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=2, locals=7, args_size=0
0: bipush 10
2: istore_0
3: bipush 10
5: istore_1
6: bipush 10
8: istore_2
9: ldc2_w #2 // double 30.0d
12: dstore_3
13: ldc2_w #2 // double 30.0d
16: dstore 5
18: return
LineNumberTable:
line 5: 0
line 6: 3
line 7: 6
line 8: 9
line 9: 13
line 10: 18
LocalVariableTable:
Start Length Slot Name Signature
3 16 0 a I
6 13 1 b I
9 10 2 c I
13 6 3 d D
18 1 5 e D
public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=0, locals=1, args_size=1
0: invokestatic #4 // Method test:()V
3: return
LineNumberTable:
line 13: 0
line 14: 3
LocalVariableTable:
Start Length Slot Name Signature
0 4 0 args [Ljava/lang/String;
}
SourceFile: "StackTest3.java"
mac@MacdeMacBook-Pro ch2_class_loader %
查看代码流程
第一步:
大白话:
在main方法里,code这里
0:invokestatic #4 // Method test:()V - 这里注释都明白告诉了,这里是个方法调用,方法名是test,返回值是V,也就是void;
第二步,查找#4:
大白话:
#4 - 就是一个方法调用,指向了StackTest3的test方法;
#4 又指向了#5.#29
#5 - 就是StackTest3这个类
#29 - 就是test这个方法
#29 又指向了#14:#8
#14:#8 - 就是test():V
总结:所有的#..都不是在内存中,而是在字节码文件中,所以,就像是一个房子的图纸,符号引用转为转为直接引用,就是把#4转为拿到在堆中的地址。
静态解析 - 类加载的时候,就将符号引用转换为直接引用;
动态解析 - 在方法里,单独来创建,单独来使用,在运行的时候才转换为直接引用,这就是动态解析。
大白话:
方法3执行的指令是10,执行方法4返回指令是11,就将指令11传给方法4,返回的时候,将指令11和执行结果一起返回。
五、图解虚拟机栈与PC协调工作过程
警告: 二进制文件MathTest包含ch2_class_loader.MathTest
Classfile /Users/mac/IdeaProjects/OOM/OOM/out/production/OOM/ch2_class_loader/MathTest.class
Last modified 2024-1-2; size 543 bytes
MD5 checksum a2e3adc0a61580b4f5260d7d2bf8ef50
Compiled from "MathTest.java"
public class ch2_class_loader.MathTest
minor version: 0
major version: 52
flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
#1 = Methodref #3.#23 // java/lang/Object."
#2 = Class #24 // ch2_class_loader/MathTest
#3 = Class #25 // java/lang/Object
#4 = Utf8
#5 = Utf8 ()V
#6 = Utf8 Code
#7 = Utf8 LineNumberTable
#8 = Utf8 LocalVariableTable
#9 = Utf8 this
#10 = Utf8 Lch2_class_loader/MathTest;
#11 = Utf8 calc
#12 = Utf8 ()I
#13 = Utf8 a
#14 = Utf8 I
#15 = Utf8 b
#16 = Utf8 c
#17 = Utf8 main
#18 = Utf8 ([Ljava/lang/String;)V
#19 = Utf8 args
#20 = Utf8 [Ljava/lang/String;
#21 = Utf8 SourceFile
#22 = Utf8 MathTest.java
#23 = NameAndType #4:#5 // "
#24 = Utf8 ch2_class_loader/MathTest
#25 = Utf8 java/lang/Object
{
public ch2_class_loader.MathTest();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: invokespecial #1 // Method java/lang/Object."
4: return
LineNumberTable:
line 3: 0
LocalVariableTable:
Start Length Slot Name Signature
0 5 0 this Lch2_class_loader/MathTest;
public int calc();
descriptor: ()I
flags: ACC_PUBLIC
Code:
stack=2, locals=4, args_size=1
0: bipush 11
2: istore_1
3: bipush 22
5: istore_2
6: bipush 33
8: istore_3
9: iload_1
10: iload_2
11: iadd
12: iload_3
13: imul
14: ireturn
LineNumberTable:
line 5: 0
line 6: 3
line 7: 6
line 8: 9
LocalVariableTable:
Start Length Slot Name Signature
0 15 0 this Lch2_class_loader/MathTest;
3 12 1 a I
6 9 2 b I
9 6 3 c I
public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=0, locals=1, args_size=1
0: return
LineNumberTable:
line 12: 0
LocalVariableTable:
Start Length Slot Name Signature
0 1 0 args [Ljava/lang/String;
}
SourceFile: "MathTest.java"
mac@MacdeMacBook-Pro ch2_class_loader %
大白话:
注意,如果整型常用值不在-128~127之间,这里就不是bipush命令了。
继续参照上面的流程,将22和33推入操作数栈,然后出栈并存放到局部变量表,结果如下:
指令10执行结果:
问题1:为什么不是在操作数栈直接计算呢?
答:这是因为有些运算比较复杂,比如a+b*c,此时拿到操作数栈拿到a和b并不能运算,而是要拿到c,先进行b和c的运算。
问题2:为什么计算时,需要现将局部变量表的变量,拿到操作数栈,而不是直接计算呢?
答:涉及到算法优先级问题,先出栈算出优先级结果,再继续计算。
六、第三步:轻松掌握本地方法(native)接口与线程调用的底层原理
七、直接内存介绍与应用简介
八、第五步:掌握方法区的功能与内部结构(上)
大白话:
程序在执行的过程中,需要大量的底层的核心类和扩展类的支持。
大白话:
1.类型信息 - 类的基础信息,比如是抽象类?
2.域信息 - 类中定义的变量
4. 5. 有的时候是单独拎出来,有的时候是归为2
大白话:
4.常见类型有七八种,都是经常使用的类型
大白话:
1.变量 -
如果带final参数,在链接阶段的准备阶段就已经完成初始化了;
如果不带final参数,在链接阶段,会首先初始化为一个默认值,到了第三阶段(初始化阶段)再将其初始化为对应的值。
九、第五步:掌握方法区的功能与内部结构(下)
大白话:
之所以变量叫域,是因为变量可以是类的,也可以是方法的,容易搞混,所以就叫域。
解释:
LineNumberTable:
line 7: 0
line 9: 6
前面那个数字对应代码的行号,后面那个数字对应指令数字。
总结:
常量池就是把常用的值、方法等放入常量池,方便后面使用。
十、【堆结构】第六步:一步一图剖析JVM的内存分配策略
解释:
上图为windows下的磁盘清理截图