这是我在看课程《黑马程序员JVM完整教程》过程中记的笔记。我觉得该课程总时不长,并且理论+实战是一个入门JVM的好课程。
若你看完该课程可以看下面几个参看书进一步深入了解JVM
- 深入理解Java虚拟机(第二版)
- 实战Java虚拟机
- 深入JAVA虚拟机第二版
这三本参考书的pdf版本已经放在下面的网盘中(只限个人看)
链接:https://pan.baidu.com/s/1iNJcbSecMwnOQuaJNxzrRQ
提取码:wcar
1.内存结构
1.1 程序计数器
Program Counter Register 程序计数器(寄存器)
下面是一条Java程序的指令:
1.2 虚拟机栈
**定义:**Java Virtual Machine Stacks (Java 虚拟机栈)
- 栈-每个线程运行时需要的内存空间。所以跟程序计数器一样线程私有的结构。
- 栈由多个栈帧组成,每个栈帧对应每个方法运行时需要的内存,包含方法参数,局部变量,返回地址等信息。
- 每个线程只能有一个活动栈帧,对应着当前正在执行的那个方法
问题辨析:
1.垃圾回收是否涉及栈内存?
答案:不会
2.内存分配越大越好吗?
答案:不是,-Xss 参数来设置栈空间,如果栈空间设置过大,会降低线程数,因为机子的物理内存空间大小固定的。
3.方法内的局部变量是否线程安全?
- 如果方法内局部变量没有逃离方法的作用访问,它是线程安全的
- 如果是局部变量引用了对象,并逃离方法的作用范围,需要考虑线程安全
栈内存溢出
java.lang.StackOverflowError
线程运行诊断
- cpu占用过多案例
- 用top定位哪个进程对cpu的占用过高
- ps H -eo pid,tid,%cpu | grep 进程id (用ps命令进一步定位是哪个线程引起的cpu占用过高)
- jstack 进程id
- 可以根据线程id 找到有问题的线程,进一步定位到问题代码的源码行号
- 程序运行很长时间没有结果案例
1.3 本地方法栈
1.4 堆
Heap 堆
特点
- 它是线程共享的,堆中对象都需要考虑线程安全的问题
- 有垃圾回收机制
堆内存溢出
java.lang.OutOfMemoryError:Java head space
堆内存诊断
案例
- 垃圾回收后,内存占用依然很高
- jps:查看当前系统中有哪些 java 进程
- jmap:查看堆内存占用情况 jmap - heap 进程id
- jconsole :执行垃圾回收,并实时查看
- jvisualvm:可视化的方式显现,jvm内存结构
1.5 方法区
定义
- Java 虚拟机有一个在所有 Java 虚拟机线程之间共享的方法区。方法区类似于传统语言的编译代码的存储区,或者类似于操作系统进程中的“文本”段。它存储每个类的结构,例如运行时常量池、字段和方法数据,以及方法和构造函数的代码,包括类和实例初始化和接口初始化中使用的特殊方法。
- 方法区是在虚拟机启动时创建的。尽管方法区在逻辑上是堆的一部分,但简单的实现可能会选择不进行垃圾收集或压缩它。本规范不要求方法区域的位置或用于管理已编译代码的策略。方法区域可以是固定大小,也可以根据计算需要扩大,如果不需要更大的方法区域,可以缩小。方法区的内存不需要是连续的。
- Java 虚拟机实现可以为程序员或用户提供对方法区域初始大小的控制,以及在方法区域大小可变的情况下,对最大和最小方法区域大小的控制。
组成:
方法区内存溢出
-
1.8 以前会导致永久代内存溢出
-
1.8 之后会导致元空间内存溢出
运行时常量池
- 常量池,就是一张表,虚拟机指令根据这张常量表找到要执行的类名、方法名、参数类型、字面量等信息
- 运行时常量池,常量池是 *.class 文件中的,当该类被加载,它的常量池信息就会放入运行时常量池,并把里面的符号地址变为真实地址
StringTable
先看几道面试题:
- 常量池中的字符串仅是符号,第一次用到时才变为对象
- 利用串池的机制,来避免重复创建字符串对象
- 字符串变量拼接的原理是 StringBuilder (1.8)
- 字符串常量拼接的原理是编译期优化
- 可以使用 intern 方法,主动将串池中还没有的字符串对象放入串池
- 1.8 将这个字符串对象尝试放入串池,如果有则并不会放入,如果没有则放入串池, 会把串池中的对象返回
- 1.6 将这个字符串对象尝试放入串池,如果有则并不会放入,如果没有会把此对象复制一份,放入串池, 会把串池中的对象返回
- 调整 -XX:StringTableSize=桶个数
1.6 直接内存
定义:Direct Memory
- 常见于 NIO 操作时,用于数据缓冲区
- 分配回收成本较高,但读写性能高
- 不受 JVM 内存回收管理
基本原理
-
不使用直接内存的时候I/O操作过程
-
使用直接内存
分配和回收原理
- 使用了 Unsafe 对象完成直接内存的分配回收,并且回收需要主动调用 freeMemory 方法
- ByteBuffffer 的实现类内部,使用了 Cleaner (虚引用)来监测 ByteBuffffer 对象,一旦ByteBuffffer 对象被垃圾回收,那么就会由 ReferenceHandler 线程通过 Cleaner 的 clean 方法调用 freeMemory 来释放直接内存
2.垃圾回收器
2.1 如何判断对象可以回收
方法一:引用计数法 (早期的Python虚拟机用过)
概念:
- 如果一个对象被引用了那么对应的引用计数器加一,反正减一,当等于零时说明该对象可以回收。
好处:
坏处:
-
如果存在循环引用,那么该方法失效了
**方法二:可达性分析算法 **
- Java 虚拟机中的垃圾回收器采用可达性分析来探索所有存活的对象
- 扫描堆中的对象,看是否能够沿着 GC Root对象 为起点的引用链找到该对象,找不到,表示可以回收
- 哪些对象可以作为 GC Root ?
四种引用
-
强引用
只有所有 GC Roots 对象都不通过【强引用】引用该对象,该对象才能被垃圾回收
-
软引用(SoftReference)
仅有软引用引用该对象时,在垃圾回收后,内存仍不足时会再次出发垃圾回收,回收软引用对象可以配合引用队列来释放软引用自身
-
弱引用(WeakReference)
仅有弱引用引用该对象时,在垃圾回收时,无论内存是否充足,都会回收弱引用对象可以配合引用队列来释放弱引用自身
-
虚引用(PhantomReference)
必须配合引用队列使用,主要配合 ByteBuffffer 使用,被引用对象回收时,会将虚引用入队,由 Reference Handler 线程调用虚引用相关方法释放直接内存
-
终结器引用(FinalReference)
无需手动编码,但其内部配合引用队列使用,在垃圾回收时,终结器引用入队(被引用对象暂时没有被回收),再由 Finalizer 线程通过终结器引用找到被引用对象并调用它的 finalize方法,第二次 GC 时才能回收被引用对象
2.2 垃圾回收算法
算法一:标记清除算法
定义:
- Mark Sweep,第一步用可达性标记算法标记可回收和不可回收对象。第二步,删除那些可回收的对象。
优点:
缺点:
算法二:标记整理算法
原理:
- 第一步用可达性标记算法标记可回收和不可回收对象。第二步,可用对象向前移动,不可用的对象删除达到整理效果。
好处:
坏处:
算法三:复制算法
原理:
- 第一步用可达性标记算法标记可回收和不可回收对象。From中的可用对象复制到TO内存块中,最后交换from和to的位置
好处:
坏处:
2.3 分代垃圾回收
-
对象首先分配在伊甸园区域
-
新生代空间不足时,触发 minor gc,伊甸园和 from 存活的对象使用 copy 复制到 to 中,存活的对象年龄加 1并且交换 from to
-
minor gc 会引发 stop the world,暂停其它用户的线程,等垃圾回收结束,用户线程才恢复运行当对象寿命超过阈值时,会晋升至老年代,最大寿命是15(4bit)
-
当老年代空间不足,会先尝试触发 minor gc,如果之后空间仍不足,那么触发 full gc,STW的时间更长
相关参数
堆初始大小 |
-Xms |
堆最大大小 |
-Xmx 或 -XX:MaxHeapSize=size |
新生代大小 |
-Xmn 或 (-XX:NewSize=size + -XX:MaxNewSize=size ) |
幸存区比例(动态) |
-XX:InitialSurvivorRatio=ratio 和 -XX:+UseAdaptiveSizePolicy |
幸存区比例 |
-XX:SurvivorRatio=ratio |
晋升阈值 |
-XX:MaxTenuringThreshold=threshold |
晋升详情 |
-XX:+PrintTenuringDistribution |
GC详情 |
-XX:+PrintGCDetails -verbose:gc |
FullGC 前 MinorGC |
-XX:+ScavengeBeforeFullGC |
2.4 垃圾回收器
-
串行
-XX:+UseSerialGC = Serial + SerialOld
-
吞吐量优先
-XX:+UseParallelGC ~ -XX:+UseParallelOldGC
-XX:GCTimeRatio=ratio
-XX:MaxGCPauseMillis=ms
-XX:ParallelGCThreads=n
- 多线程
- 堆内存较大,多核 cpu
- 让单位时间内,STW 的时间最短 0.2 0.2 = 0.4,垃圾回收时间占比最低,这样就称吞吐量高
-
响应时间优先
-XX:+UseConcMarkSweepGC ~ -XX:+UseParNewGC ~ SerialOld
-XX:ParallelGCThreads=n ~ -XX:ConcGCThreads=threads
-XX:CMSInitiatingOccupancyFraction=percent
-XX:+CMSScavengeBeforeRemark
-
G1
定义:Garbage First
-
2004 论文发布
-
2009 JDK 6u14 体验
-
2012 JDK 7u4 官方支持
-
2017 JDK 9 默认
适用场景
-
同时注重吞吐量(Throughput)和低延迟(Low latency),默认的暂停目标是 200 ms
-
超大堆内存,会将堆划分为多个大小相等的 Region
-
整体上是 标记+整理 算法,两个区域之间是 复制 算法
相关 JVM 参数
-XX:+UseG1GC
-XX:G1HeapRegionSize=size
-XX:MaxGCPauseMillis=time
G1 垃圾回收阶段
-
Young Collection
-
Young Collection + CM
-
Mixed Collection
-
会对 E、S、O 进行全面垃圾回收
- 最终标记(Remark)会 STW
- 拷贝存活(Evacuation)会 STW
-
-XX:MaxGCPauseMillis=ms
-
Full GC
-
SerialGC
- 新生代内存不足发生的垃圾收集 - minor gc
- 老年代内存不足发生的垃圾收集 - full gc
-
ParallelGC
-
CMS
-
G1
Young Collection 跨代引用
Remark
JDK 8u20 字符串去重
-
优点:节省大量内存
-
缺点:略微多占用了 cpu 时间,新生代回收时间略微增加
-
-XX:+UseStringDeduplication
-
String s1 = new String("hello"); // char[]{'h','e','l','l','o'}
String s2 = new String("hello"); // char[]{'h','e','l','l','o'}
-
将所有新分配的字符串放入一个队列
-
当新生代回收时,G1并发检查是否有字符串重复
-
如果它们值一样,让它们引用同一个 char[]
-
注意,与 String.intern() 不一样
JDK 8u40 并发标记类卸载
所有对象都经过并发标记后,就能知道哪些类不再被使用,当一个类加载器的所有类都不再使用,则卸载它所加载的所有类 -XX:+ClassUnloadingWithConcurrentMark 默认启用
JDK 8u60 回收巨型对象
JDK 9 并发标记起始时间的调整
JDK 9 更高效的回收
- 250+增强
- 180+bug修复
- https://docs.oracle.com/en/java/javase/12/gctuning
2.5 垃圾回收调优
预备知识
- 掌握 GC 相关的 VM 参数,会基本的空间调整
- 掌握相关工具
- 明白一点:调优跟应用、环境有关,没有放之四海而皆准的法则
调优领域
确定目标
- 【低延迟】还是【高吞吐量】,选择合适的回收器
- CMS,G1,ZGC
- ParallelGC
- Zing
最快的 GC 是不发生 GC
新生代调优
新生代的特点
- 所有的 new 操作的内存分配非常廉价
- TLAB thread-local allocation buffer
- 死亡对象的回收代价是零
- 大部分对象用过即死
- Minor GC 的时间远远低于 Full GC
越大越好吗?
-Xmn
Sets the initial and maximum size (in bytes) of the heap for the young generation (nursery).GC is performed in this region more often than in other regions. If the size for the young generation is too small, then a lot of minor garbage collections are performed. If the size is too large, then only full garbage collections are performed, which can take a long time to complete.Oracle recommends that you keep the size for the young generation greater than 25% and less than 50% of the overall heap size.
-XX:MaxTenuringThreshold=threshold
-XX:+PrintTenuringDistribution
Desired survivor size 48286924 bytes, new threshold 10 (max 10)
- age 1: 28992024 bytes, 28992024 total
- age 2: 1366864 bytes, 30358888 total
- age 3: 1425912 bytes, 31784800 total
...
老年代调优
以 CMS 为例
-
CMS 的老年代内存越大越好
-
先尝试不做调优,如果没有 Full GC 那么已经…,否则先尝试调优新生代
-
观察发生 Full GC 时老年代内存占用,将老年代内存预设调大 1/4 ~ 1/3
-XX:CMSInitiatingOccupancyFraction=percent
案例
3.类加载与字节码技术
3.1 类文件结构
一个简单的 HelloWorld.java
package org.example;
/**
* HelloWorld 示例
*/
public class HelloWorld {
public static void main(String[] args) {
System.out.println("hello world");
}
}
编译为 HelloWorld.class 在Idea中打开后是这个样子的:
HelloWorld.class 文件转二进制文件:
根据 JVM 规范,类文件结构如下
ClassFile {
u4 magic; //魔数
u2 minor_version; //jdk的副版本号
u2 major_version: //jdk的主版本号
u2 constant_pool_count: //常量池数量
cp_info constant_pool[constant_pool_count-1]: //常量池具体信息
u2 access_flags: //访问权限标识
u2 this_class: //类名
u2 super_class: //父类名
u2 interfaces_count: //接口数量
u2 interfaces[interfaces_count]: //接口类信息
u2 fields_count: //字段数量
field_info fields[fields_count]: //字段信息
u2 methods_count: //方法数量
method_info methods[methods_count]: //方法具体信息
u2 attributes_count: //属性数量
attribute_info attributes[attributes_count]: //属性具体信息
}
-
魔数
- 0~3 字节,表示它是否是【class】类型的文件
-
版本
- 4~7 字节,表示类的版本 00 34(52) 表示是 Java 8
-
常量池
Constant Type |
Value |
CONSTANT_Class |
7 |
CONSTANT_Fieldref |
9 |
CONSTANT_Methodref |
10 |
CONSTANT_InterfaceMethodref |
11 |
CONSTANT_String |
8 |
CONSTANT_Integer |
3 |
CONSTANT_Float |
4 |
CONSTANT_Long |
5 |
CONSTANT_Double |
6 |
CONSTANT_NameAndType |
12 |
CONSTANT_Utf8 |
1 |
CONSTANT_MethodHandle |
15 |
CONSTANT_MethodType |
16 |
CONSTANT_InvokeDynamic |
18 |
-
8~9 字节,表示常量池长度,00 22(34) 表示常量池有 #1~#33项,注意 #0 项不计入,也没有值
-
第#1项 0a 表示常量池中的类型,因为0a(10) 对应上面表格中的CONSTANT_Methodref 类型,所以它表示 Method 信息,00 06 和 00 14(20) 表示它引用了常量池中 #6 和 #20 项来获得这个方法的【所属类】和【方法名】
-
第#2项 09 表示一个 Field 信息,00 15(21)和 00 16(22) 表示它引用了常量池中 #21 和 # 22 项来获得这个成员变量的【所属类型】和【成员变量名】
-
第#3项 08 表示一个字符串常量名称,00 17(23)表示它引用了常量池中 #23 项
-
第#4项 0a 表示一个 Method 信息,00 18(24) 和 00 19(25)表示它引用了常量池中 #24 和 #25项来获得这个方法的【所属类】和【方法名】
-
第#5项 07 表示一个 Class 信息,00 1a(26) 表示它引用了常量池中 #26 项
-
第#6项 07 表示一个 Class 信息,00 1b(27) 表示它引用了常量池中 #27 项
-
第#7项 01 表示一个 utf8 串,00 06 表示长度,3c 69 6e 69 74 3e 是【 】
-
第#8项 01 表示一个 utf8 串,00 03 表示长度,28 29 56 是【()V】其实就是表示无参、无返回值
-
第#9项 01 表示一个 utf8 串,00 04 表示长度,43 6f 64 65 是【Code】
-
第#10项 01 表示一个 utf8 串,00 0f(15) 表示长度,4c 69 6e 65 4e 75 6d 62 65 72 54 61 62 6c 65是【LineNumberTable】
-
第#11项 01 表示一个 utf8 串,00 12(18) 表示长度,4c 6f 63 61 6c 56 61 72 69 61 62 6c 65 54 61 62 6c 65是【LocalVariableTable】
-
第#12项 01 表示一个 utf8 串,00 04 表示长度,74 68 69 73 是【this】
-
第#13项 01 表示一个 utf8 串,00 18(24) 表示长度,是【Lorg/example/HeloWorld;】
-
第#14项 01 表示一个 utf8 串,00 04 表示长度,6D 61 69 6E是【main】
-
第#15项 01 表示一个 utf8 串,00 16(22) 表示长度,是【([Ljava/lang/String;)V】其实就是参数为字符串数组,无返回值
-
第#16项 01 表示一个 utf8 串,00 04 表示长度,是【args】
-
第#17项 01 表示一个 utf8 串,00 13(19) 表示长度,是【[Ljava/lang/String;】
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-GHE5d5zF-1679980231499)(https://pic-1307534554.cos.ap-chongqing.myqcloud.com/img/image-20230322193804022.png)]
-
第#18项 01 表示一个 utf8 串,00 0A(10) 表示长度,是【SourceFile】
-
第#19项 01 表示一个 utf8 串,00 0f(15) 表示长度,是【HelloWorld.java】
-
第#20项0c 表示一个 【名+类型】,00 07 00 08 引用了常量池中 #7 #8 两项
-
第#21项 07 表示一个 Class 信息,00 1c(28) 引用了常量池中 #28 项
-
第#22项 0c 表示一个【名+类型】00 1d(29) 00 1e (30)引用了常量池中 #29 #30两项
-
第#23项 01 表示一个 utf8 串,00 0b(11) 表示长度,是【hello world】
-
第#24项 07 表示一个 Class 信息,00 1F(31) 引用了常量池中 #31 项
-
第#25项 0c 表示一个【名+类型】00 20(32) 00 21(33)引用了常量池中 #32 #33 两项
-
第#26项 01 表示一个 utf8 串,00 16(22) 表示长度,是【org/example/HelloWorld】
-
第#27项 01 表示一个 utf8 串,00 10(16) 表示长度,是【java/lang/Object】
-
第#28项 01 表示一个 utf8 串,00 10(16) 表示长度,是【java/lang/System】
-
第#29项 01 表示一个 utf8 串,00 03 表示长度,是【out】
-
第#30项 01 表示一个 utf8 串,00 15(21) 表示长度,是【Ljava/io/PrintStream;】
-
第#31项 01 表示一个 utf8 串,00 13(19) 表示长度,是【java/io/PrintStream】
-
第#32项 01 表示一个 utf8 串,00 07 表示长度,是【println】
-
第#33项 01 表示一个 utf8 串,00 15(21) 表示长度,是【(Ljava/lang/String;)V】
-
访问标识与继承信息
访问标识符:
00 21 表示等价于【0x0001 + 0x0020】表示该Class是一个公共的类【public class】
Flag Name |
Value |
Interpretation |
ACC_PUBLIC |
0x0001 |
Declared public ; may be accessed from outside its package. |
ACC_FINAL |
0x0010 |
Declared final ; no subclasses allowed. |
ACC_SUPER |
0x0020 |
Treat superclass methods specially when invoked by the invokespecial instruction. |
ACC_INTERFACE |
0x0200 |
Is an interface, not a class. |
ACC_ABSTRACT |
0x0400 |
Declared abstract ; must not be instantiated. |
ACC_SYNTHETIC |
0x1000 |
Declared synthetic; not present in the source code. |
ACC_ANNOTATION |
0x2000 |
Declared as an annotation type. |
ACC_ENUM |
0x4000 |
Declared as an enum type. |
类名:
00 05 表示根据常量池中 #5 找到本类全限定名
父类名:
00 06 表示根据常量池中 #6 找到父类全限定名
接口数量:
00 00 表示接口的数量,本类为 0
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qEDlWg4V-1679980231512)(https://pic-1307534554.cos.ap-chongqing.myqcloud.com/img/image-20230322211704581.png)]
-
Field 信息
00 00 表示成员变量数量,本类为 0
FieldType |
Type |
Interpretation |
B |
byte |
signed byte |
C |
char |
Unicode character code point in the Basic Multilingual Plane,encoded with UTF-16 |
D |
double |
double-precision flfloating-point value |
F |
float |
single-precision flfloating-point value |
I |
int |
integer |
J |
long |
long integer |
L ClassName ; |
reference |
an instance of class ClassName
|
S |
short |
signed short |
Z |
boolean |
true or false |
[ |
reference |
one array dimension |
-
Method 信息
方法数量:
00 02 表示方法数量,本类为 2
方法信息:
一个方法由 访问修饰符,名称,参数描述,方法属性数量,方法属性组成:
方法一:
-
访问修饰符: 00 01 ,本类中是 【ACC_PUBLIC 】public
-
方法名称:00 07 【】 代表引用了常量池 #07 项作为方法名称
-
参数描述:00 08 【()V】代表引用了常量池 #08 项作为方法参数描述
-
方法属性数量:00 01 代表方法属性数量,本方法是 1
-
代表方法属性:
-
00 09 表示引用了常量池 #09 项,发现是【Code】属性
-
00 00 00 2f 表示此属性的长度是 47
-
00 01 表示【操作数栈】最大深度 本方法是 1
-
00 01 表示【局部变量表】最大槽(slot)数 本方法是1
-
00 00 00 05 表示字节码长度,本例是 5
-
2A B7 00 01 B1 是字节码指令
-
00 00 00 02 表示方法细节属性数量,本例是 2
-
00 0A 表示引用了常量池 #10 项,发现是【LineNumberTable】属性
-
00 00 00 06 表示此属性的总长度,本例是 6
-
00 01 表示【LineNumberTable】长度
-
00 00 表示【字节码】行号 00 09 表示【java 源码】行号
-
00 0b 表示引用了常量池 #11 项,发现是【LocalVariableTable】属性
-
00 00 00 0c 表示此属性的总长度,本例是 12
-
00 01 表示【LocalVariableTable】长度
-
00 00 表示局部变量生命周期开始,相对于字节码的偏移量
-
00 05 表示局部变量覆盖的范围长度
-
00 0c 表示局部变量名称,本例引用了常量池 #12 项,是【this】
-
00 0d 表示局部变量的类型,本例引用了常量池 #13 项,是【Lorg/example/HeloWorld;】
-
00 00 表示局部变量占有的槽位(slot)编号,本例是 0
方法二:
- 访问修饰符:00 09 ,本类中是【00 01 + 00 08】 public static
- 名称:00 0E 代表引用了常量池 #14 项作为方法名称【main】
- 参数描述:00 0F 代表引用了常量池 #15 项作为方法参数描述【([Ljava/lang/String;)V】
- 方法属性数量: 00 01 代表方法属性数量,本方法是 1
- 方法属性组成:
- 00 09 表示引用了常量池 #09 项,发现是【Code】属性
- 00 00 00 37 表示此属性的长度是 55
- 00 02 表示【操作数栈】最大深度
- 00 01 表示【局部变量表】最大槽(slot)数
- 00 00 00 09 表示字节码长度,本例是 9
- b2 00 02 12 03 b6 00 04 b1 是字节码指令
- 00 00 00 02 表示方法细节属性数量,本例是 2
- 00 0a 表示引用了常量池 #10 项,发现是【LineNumberTable】属性
- 00 00 00 0a 表示此属性的总长度,本例是 10
- 00 02 表示【LineNumberTable】长度
- 00 00 表示【字节码】行号 00 0B 表示【java 源码】行号
- 00 08 表示【字节码】行号 00 0C 表示【java 源码】行号
- 00 0b 表示引用了常量池 #11 项,发现是【LocalVariableTable】属性
- 00 00 00 0c 表示此属性的总长度,本例是 12
- 00 01 表示【LocalVariableTable】长度
- 00 00 表示局部变量生命周期开始,相对于字节码的偏移量
- 00 09 表示局部变量覆盖的范围长度
- 00 10 表示局部变量名称,本例引用了常量池 #16 项,是【args】
- 00 11 表示局部变量的类型,本例引用了常量池 #17 项,是【[Ljava/lang/String;】
- 00 00 表示局部变量占有的槽位(slot)编号,本例是 0
-
附加属性
3.2 字节码指令
3.2.1 入门
接着上一节,研究一下两组字节码指令,一个是
public org.example.HeloWorld();构造方法的字节码指令: 2A B7 00 01 B1
- 2a => aload_0 加载 slot 0 的局部变量,即 this,做为下面的 invokespecial 构造方法调用的参数
- b7 => invokespecial 预备调用构造方法,哪个方法呢?
- 00 01 引用常量池中 #1 项,即【 Method java/lang/Object.“”
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)