| -XX:G1NewSizePercent=5, -XX:G1MaxNewSizePercent=60 | 新生代 region 最小占整堆的 5%,最大 60%,超出则触发新生代 GC |
| -XX:G1HeapWastePercent=5 | 允许浪费的堆内存占比,可回收内存低于 5% 则不进行混合回收 |
| -XX:G1MixedGCLiveThresholdPercent=85 | 老年代存活对象占比超 85%,回收价值低,暂不回收 |
| -XX:G1MixedGCCountTarget=8 | 单次收集中混合回收次数 |
22、内存分配策略
使用 Serial 收集器 -XX:+UseG1GC 演示
1. 对象优先分配在 Eden 区
新对象在 Eden 区分配,空间不足则触发 Minor GC,存活对象拷贝到 To Survivor,若还是内存不足则通过分配担保机制转移到老年区,依旧不足才 OOM
byte[] buf1 = new byte[6 * MB];
byte[] buf2 = new byte[6 * MB]; // 10MB 的 eden 区剩余 4MB,空间不足,触发 minor GC
// java -verbose:gc -Xms20m -Xmx20m -Xmn10m -XX:+PrintGCDetails -XX:+UseSerialGC com.ch03.Allocation
// minor gc 后新生代内存从 6M 降到 0.2M,存活对象移到了老年区,总的堆内存用量依旧是 6MB
[GC (Allocation Failure) [DefNew: 6823K->286K(9216K), 0.002 secs] 6823K->6430K(19456K), 0.002 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
Heap
def new generation total 9216K, used 6513K
eden space 8192K, 76% used // buf2
from space 1024K, 28% used
to space 1024K, 0% used
tenured generation total 10240K, used 6144K
the space 10240K, 60% used // buf1
2. 大对象直接进入老年区
对于 Serial, ParNew,可配置超过阈值 -XX:PretenureSizeThreshold 的大对象(连续内存),直接在老年代中分配,避免触发 minor gc,导致 Eden 和 Survivor 产生大量的内存复制操作
byte[] buf1 = new byte[4 * MB];
// java -verbose:gc -Xms20m -Xmx20m -Xmn10m -XX:+PrintGCDetails -XX:+UseSerialGC
// -XX:PretenureSizeThreshold=3145728 com.ch03.Allocation // 3145728 即 3MB
Heap
def new generation total 9216K, used 843K
eden space 8192K, 10% used
from space 1024K, 0% used
to space 1024K, 0% used
tenured generation total 10240K, used 4096K
the space 10240K, 40% used // buf1
3. 长期存活的对象进入老年代
对象头中 4bit 的 age 字段存储了对象当前 GC 分代年龄,当超过阈值-XX:MaxTenuringThreshold(默认 15,也即 age 字段最大值)后,将晋升到老年代,可搭配-XX:+PrintTenuringDistribution观察分代分布
byte[] buf1 = new byte[MB / 16];
byte[] buf2 = new byte[4 * MB];
byte[] buf3 = new byte[4 * MB]; // 触发 minor gc
buf3 = null;
buf3 = new byte[4 * MB];
// java -verbose:gc -Xms20m -Xmx20m -Xmn10m -XX:+PrintGCDetails -XX:+UseSerialGC
// -XX:MaxTenuringThreshold=1 -XX:+PrintTenuringDistribution com.ch03.Allocation
[GC (Allocation Failure) [DefNew
Desired survivor size 524288 bytes, new threshold 1 (max 1)
- age 1: 359280 bytes, 359280 total
: 4839K->350K(9216K)] 4839K->4446K(19456K), 0.0017247 secs]
// 至此,buf1 熬过了第一次收集,age=1
[GC (Allocation Failure) [DefNew
Desired survivor size 524288 bytes, new threshold 1 (max 1): 4446K->0K(9216K)] 8542K->4438K(19456K)]
Heap
def new generation total 9216K, used 4178K
eden space 8192K, 51% used
from space 1024K, 0% used // buf1 在第二轮收集中被提前晋升
to space 1024K, 0% used
tenured generation total 10240K, used 4438K
the space 10240K, 43% used
4. 分代年龄动态判定
-XX:MaxTenuringThreshold并非晋升的最低硬性门槛,当 Survivor 中同龄对象超 50% 后,大于等于该年龄的对象会被自动晋升,哪怕还没到阈值
5. 空间分配担保
老年代作为 To Survivor 区的担保区域,当 Eden + From Survivor 中存活对象的总大小超出 To Survivor 时,将尝试存入老年代。JDK6 之后,只要老年代的连续空间大于新生代对象的总大小,或之前晋升的平均大小,则只会进行 Minor GC,否则进行 Full GC
另外,如果您正在学习Spring Cloud,推荐一个连载多年还在继续更新的免费教程:https://blog.didispace.com/spring-cloud-learning/
23、类文件结构
Class 文件实现语言无关性,JVM 实现平台无关性,参考《Java 虚拟机规范》

一个 Class 文件描述了一个类或接口的明确定义,文件内容是一组以 8 字节为单位的二进制流,各数据项间没有分隔符,超过 8 字节的数据项按 Big-Endian 切分后存储。数据项分两种:
-
无符号数:描述基本类型;用
u1,u2,u4,u8分别表示1,2,4,8字节长度的无符号数;存储数字值、索引序号、UTF-8 编码值等 -
表:由无符号数、其他表嵌套构成的复合类型;约定
_info后缀;存储字段类型、方法签名等
24、结构定义
25、语法
参考文档:The class File Format
ClassFile {
u4 magic; // 魔数
u2 minor_version; // 版本号
u2 major_version;
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];
}
-
magic:魔数,简单识别
*.class文件,值固定为0xCAFEBABE -
minor_version, major_version:Class 文件的次、主版本号
-
constant_pool_count:常量池大小+1
-
constant_pool:常量池,索引从 1 开始,0 值被预留表示不引用任何常量池中的任何常量;常量分两类
-
字面量:如 UTF8 字符串、int、float、long、double 等数字常量
-
符号引用:类、接口的全限定名、字段名与描述符、方法类型与描述符等 现有常量共计 17 种,常量间除了都使用
u1 tag前缀标识常量类型外,结构互不相同,常见的有: -
CONSTANT_Utf8_info:保存由 UTF8 编码的字符串
CONSTANT_Utf8_info {
u1 tag; // 值为 1
u2 length; // bytes 数组长度,u2 最大值 65535,即单个字符串字面量不超过 64KB
u1 bytes[length]; // 长度不定的字节数组
}
CONSTANT_Class_info:表示类或接口的符号引用
CONSTANT_Class_info {
u1 tag; // 值为 7
u2 name_index; // 指向全限定类名的 Utf8_info // 常量间存在层级组合关系
}
CONSTANT_Fieldref_info, CONSTANT_Methodref_info, CONSTANT_NameAndType_info:成员变量、成员方法及其类型描述符
CONSTANT_Fieldref_info {
u1 tag; // 值为 9
u2 class_index; // 所属类
u2 name_and_type_index; // 字段的名称、类型描述符
}
CONSTANT_Methodref_info {
u1 tag; // 值为 10
u2 class_index; // 所属类
u2 name_and_type_index; // 方法的名称、签名描述符
}
CONSTANT_NameAndType_info {
u1 tag; // 值为 12
u2 name_index; // 字段或方法的名称
u2 descriptor_index; // 类型描述符
}
如上只列举了其中 5 种常量的结构,可见常量间通过组合的方式,来描述层级关系
-
access_flags:类的访问标记,有 16bit,每个标记对应一个位,比如
ACC_PUBLIC对应0x0001,表示类被 public 修饰;其他 8 个标记参考Opcodes.ACC_XXX -
this_class, super_class:指向本类、唯一父类的 Class_info 符号常量
-
interface_count, interfaces:描述此类实现的多个接口信息
-
fields_count, fields:字段表;描述类字段、成员变量的个数及详细信息
field_info {
u2 access_flags; // 作用域、static,final,volatile 等访问标记
u2 name_index; // 字段名
u2 descriptor_index; // 类型描述符
u2 attributes_count; // 字段的属性表
attribute_info attributes[attributes_count];
}
类型描述符简化描述了字段的数据类型、方法的参数列表及返回值,与 Java 中的类型对于关系如下:
-
基本类型:
Z:boolean, B:byte, C:char, S:short, I:int, F:float, D:double, J:long -
void 及引用类型:
V:void -
引用类型:
L:_,类名中的 . 替换为 /,添加 ; 分隔符,如 Object 类描述为Ljava/lang/Object; -
数组类型:每一维用一个前置
[表示 示例:boolean regionMatch(int, String, int, int)对应描述符为(ILjava/lang/String;II)Z -
methods_count, methods:方法表;完整描述各成员方法的修饰符、参数列表、返回值等签名信息
method_info {
u2 access_flags; // 访问标记
u2 name_index; // 方法名
u2 descriptor_index; // 方法描述符
u2 attributes_count; // 方法属性表
attribute_info attributes[attributes_count];
}
字段表、方法表都可以带多个属性表,如常量字段表、方法字节码指令表、方法异常表等。属性模板:
attribute_info {
u2 attribute_name_index; // 属性名
u4 attribute_length; // 属性数据长度
u1 info[attribute_length]; // 其他字段,各属性的结构不同
}
属性有 20+ 种,此处只记录常见的三种
- Code 属性:存储方法编译后的字节码指令
Code_attribute {
u2 attribute_name_index; // 属性名,指向的 Utf8_info 值固定为 “Code”
u4 attribute_length; // 剩下字节长度
u2 max_stack; // 操作数栈最大深度,对于此方法的栈帧中操作数栈的深度
u2 max_locals; // 以 slot 变量槽为单位的局部变量表大小,存储隐藏参数 this,实参列表,catch 参数,局部变量等
u4 code_length; // 字节码指令总长度
u1 code[code_length]; // JVM 指令集大小 200+,单个指令的编号用 u1 描述
u2 exception_table_length; // 异常表,描述方法内各指令区间产生的异常及其 handler 地址
{ u2 start_pc; // catch_type 类型的异常,会在 [start_pc, end_pc) 指令范围内抛出
u2 end_pc;
u2 handler_pc; // 若抛出此异常,则 goto 到 handler_pc 处执行
u2 catch_type;
} exception_table[exception_table_length];
u2 attributes_count; // Code 属性自己的属性
attribute_info attributes[attributes_count];
}
- LineNumberTable 属性:记录 Java 源码行号与字节码行号的对应关系,用于抛异常时显示堆栈对应的行号等信息。可作为 Code 属性的子属性
LineNumberTable_attribute {
u2 attribute_name_index; u4 attribute_length;
u2 line_number_table_length;
{ u2 start_pc; // 字节码指令区间开始位置
u2 line_number; // 对应的源码行号
} line_number_table[line_number_table_length];
}
- LocalVariableTable 属性:记录 Java 方法中局部变量的变量名,与栈帧局部变量表中的变量的对应关系,用于保留各方法有意义的变量名称
LocalVariableTable_attribute {
u2 attribute_name_index; u4 attribute_length;
u2 local_variable_table_length;
{ u2 start_pc; // 局部变量生命周期开始的字节码偏移量
u2 length; // 向后生命周期覆盖的字节码长度
u2 name_index; // 变量名
u2 descriptor_index; // 类型描述符
u2 index; // 对应的局部变量表中的 slot 索引
} local_variable_table[local_variable_table_length];
}
其他属性直接参考 JVM 文档
26、示例
源码:com/cls/Structure.java
package com.cls;
public class Structure {
public static void main(String[] args) {
System.out.println(“hello world”);
}
}
javac -g:lines com/cls/Structure.java 编译后,参考 javap 反编译得到的正确结果,od -x --endian=big Structure.class 得出 class 文件内容的十六进制表示,解读如下:
cafe babe # 1. u4 魔数,标识 class 文件类型
0000 0034 # 2. u2,u2 版本号,52 JDK8
# 3. 常量池
—1—
001f # u2 constant_pool_count,31 项(从 1 开始计数,0 预留)
0a # u1 tag,10,Methoddef_info,成员方法结构
0006 # u2 index,6,所属类的 Class_info 在常量池中的编号 ## java/lang/Object
0011 # u2 index,17,此方法 NameAndType 编号 ## 😦)V
—2—
09 # 9,Fileddef_info,成员变量结构
0012 # u2 index,18,所属类 Class_info 编号 ## java/lang/System
0013 # u2 index,19,此字段 NameAndType 编号 ## out:Ljava/io/PrintStream
—3—
08 # 8,String_info,字符串
0014 # u2 index,20,字面量编号 ## hello world
—4—
0a
0015 # 21 ## java/io/PrintStream
0016 # 22 ## println:(Ljava/lang/String;)V
—5—
07 # Class_info,全限定类名
0017 # u2 index,23,字面量编号 ## com/cls/Structure
—6—
07 # 7,Class_info,类引用
0018 # 24 ## java/lang/Object
—7—
01 # Utf8_info,UTF8 编码的字符串
0006 # u2 length,6,字符串长度
3c 69 6e 69 74 3e # 字面量值 ## “”
—8-16—
01 0003 282956 ## “()V”
01 0004 436f6465 ## “Code”
01 000f 4c696e654e756d6265725461626c65 ## “LineNumberTable”
01 0004 6d61696e ## “main”
01 0016 285b4c6a6176612f6c616e672f537472696e673b2956 ## “([Ljava/lang/String;)V”
01 0010 4d6574686f64506172616d6574657273 ## “MethodParameters”
01 0004 61726773 ## “args”
01 000a 536f7572636546696c65 ## “SourceFile”
01 000e 5374727563747572652e6a617661 ## “Structure.java”
—17—
0c # 12,NameAndType,名字及类型描述符
0007 # u2 index,7,字段或方法名字面量编号 ##
0008 # u2 index,8,字段或方法结构编号 ## ()V
—18—
07 0019 # 25 ## java/lang/System
—19—
0c
001a 001b # 26:27 ## out:Ljava/io/PrintStream;
—20—
01 000b 68656c6c6f20776f726c64 ## “hello world”
—21–
07 001c # 28 ## java/io/PrintStream
—22–
0c
001d 001e # 29:30 ## println:(Ljava/lang/String;)V
—23-31—
01 0011 636f6d2f636c732f537472756374757265 ## “com/cls/Structure”
01 0010 6a6176612f6c616e672f4f626a656374 ## "java/lang/Object "
01 0010 6a6176 612f 6c61 6e67 2f53 7973 7465 6d ## “java/lang/System”
01 0003 6f7574 ## “out”
01 0015 4c6a6176612f696f2f5072696e7453747265616d3b ## “Ljava/io/PrintStream;”
01 0013 6a6176612f696f2f5072696e7453747265616d ## “java/io/PrintStream”
01 0007 7072696e746c6e ## “println”
01 0015 284c6a6176612f6c616e672f537472696e673b2956 ## “(Ljava/lang/String;)V”
0021 # 4. u2,access_flags ## ACC_PUBLIC | ACC_SUPER
0005 # 5. u2, this_class,5 ## --5.Class_info–> com/cls/Structure
0006 # 6. u2, super_class, 6 ## --6.Class_info–> java/lang/Object
0000 # 7. u2, interface_count, 0
0000 # 8. u2, fields_count, 0
0002 # 9. methods count, 2
# 方法一
0001 # u2, access_flags, ACC_PUBLIC
0007 # u2, name_index, 7 ##
0008 # u2, descriptor_index, 8 ## ()V
0001 # u2, attribute_count, 1
0009 # u2, attribute_name_index, 9 ## Code 属性
0000 001d # u4, attribute_length, 30
0001 # u2, max_stack, 1
0001 # u2, max_locals, 1
0000 0005 # u4, code_array_length, 5
2a # u1, aload_0 ## 将第 0 个 slot 中的变量 this 入栈
b7 0001 # u1, invokespecial ## 执行从 Object 继承的
b1 # u1, return ## 返回 void
0000 # u2, exception_table_length, 0 ## exception table 为空,无异常
0001 # u2, attributes_count, 1 ## Code 属性本身的子属性
000a # 10 ## LineNumberTable 属性
0000 0006 # 6
0001 # u2, line_number_table_length, 1
0000 # u2, start_pc, 0
0003 # u2, line_number, 3
# 方法二
0009 # access_flags ## ACC_PIBLIC | ACC_STATIC
000b # name_index, 11 ## main
000c # descriptor_index, 12 ## ([Ljava/lang/String;)V
0002 # attribute_count, 2
0009 # attribute_name_index, 9 ## Code
0000 0025 # attribute_length, 37
0002 # max_stack, 2
0001 # max_locals, 1
0000 0009 # code_array_length, 9
b2 0002 # getstatic, 2 ## Field: java/lang/System.out:Ljava/io/PrintStream; // 加载静态对象变量
12 03 # ldc, 3 ## String: “hello world” // 将常量参数入栈
b6 0004 # invokevirtual, 4 ## Method: java/io/PrintStream.println:(Ljava/lang/String;)V // 执行方法
b1 # return
0000 # exception_table_length, 0
0001 # attributes_count, 1
000a # 10 ## LineNumberTable
0000 000a # 10
0002 # line_number_table_length, 2
0000 0005 # 0 -> 5
0008 0006 # 8 -> 6
27、字节码指令
JVM 面向操作数栈(operand stack)设计了指令集,每个指令由 1 字节的操作码(opcode)表示,其后跟随 0 个或多个操作数(operand),指令集列表参考 Java bytecode instruction listings
-
大部分与数据类型相关的指令,其操作码符号都会带类型前缀,如 i 前缀表示操作 int,剩余对应关系为
b:byte, c:char, s:short, f:float, d:double, l:long, a:reference -
由于指令集大小有限(256个),故
boolean, byte, char, short会被转为int运算
字节码可大致分为六类:
- 加载和存储指令:将变量从局部变量表 slot 加载到操作数栈的栈顶,反向则是存储
// 将 slot 0,1,2,3,N 加载到栈顶,T 表示类型简记前缀,可取 i,l,f,d,a
Tload_0, Tload_1, Tload_2, Tload_3, Tload n
// 将栈顶数据写回指定的 slot
Tstore_0, Tstore_1, Tstore_2, Tstore_3, Tstore n
// 将不同范围的常量值加载到栈顶,由于 0~5 常量过于常用,有单独对应的指令,ldc 则加载普通常量
bipush, sipush, Tconst_[0,1,2,3,4,5], aconst_null, ldc
- 运算指令
Tadd, Tsub, Tmul, Tdiv, Trem // 算术运算:加减乘除,取余
Tneg, Tor, Tand, Txor // 位运算:取反、或、与、异或
dcmpg, dcmpl, fcmpg, fcmpl, lcmp // 比较运算:后缀 g 即 greater, l 即 less than
iinc // 局部自增运算,与 iload 搭配使用
- 强制类型转换指令:窄化转换为 T 类型(长度为 N)时,会直接丢弃除了低 N 位外的其他位,可能会导致数据溢出、正负号不确定,浮点数转整型则会丢失精度
i2b // int -> byte
i2c, i2s; l2i, f2i, d2i; d2l, f2l; d2f
- 对象创建与访问指令:类实例、数组都是对象,存储结构不同,创建和访问指令有所区别
new // 创建类实例
newarray, annewarray, multianewarry // 创建基本类型数组、引用类型数组、多维引用类型数组
getfield, putfield; getstatic, putstatic // 读写类实例字段;读写类静态字段
Taload, Tastore; arraylength // 读写数组元素;计算数组长度
instanceof; checkcast // 校验对象是否为类实例;执行强制转换
- 操作数栈管理指令
pop, pop2 // 弹出栈顶 1,2 元素
dup, dup2; swap // 复制栈顶 1,2 个元素并重新入栈;交换栈顶两个元素
- 控制转移指令:判断条件成立,则跳转到指定的指令行(修改 PC 指向)
if_<icmpeq,icmpne;icmplt,icmple;icmpgt,icmpge;acmpe,acmpne> // 整型比较,引用相等性判断
if<eq,lt,le,gt,ge,null,nonnull> // 搭配其他类型的比较运算指令使用
- 方法调用与返回指令
invokevirtual // 根据对象的实际类型进行分派,调用对应的方法(比如继承后方法重写)
invokespecial // 调用特殊方法,如 ()V, ()V 等初始化方法、私有方法、父类方法
invokestatic // 调用类的静态方法
invokeinterface // 调用接口方法(实现接口的类对象,但被声明为接口类型,调用方法)
invokedynamic // TODO
Treturn, return // 返回指定类型,返回 void
-
异常处理指令:
athrow抛出异常,异常处理则由 exception_table 描述 -
同步指令:synchronized 对象锁由
monitorenter, monitorexit搭配对象的 monitor 锁共同实现
28、类加载
29、类加载过程

1. 加载
原理:委托 ClassLoader 读取 Class 二进制字节流,载入到方法区内存,并在堆内存中生成对应的java.lang.Class对象相互引用

2. 验证
校验字节流确保符合 Class 文件格式,执行语义分析确保符合 Java 语法,校验字节码指令合法性
另外,如果您正在学习Spring Cloud,推荐一个连载多年还在继续更新的免费教程:https://blog.didispace.com/spring-cloud-learning/
3. 准备
在堆中分配类变量(static)内存并初始化为零值,主义还没到执行 putstatic 指令赋值的初始化阶段,但静态常量属性除外:
public class ClassX {
final static int n = 2; // 常量的值在编译期就已知,准备阶段完成赋值,值存储在 ConstantValue
final static String str = “str”; // 字符串静态常量同理
}
static final java.lang.String str;
descriptor: Ljava/lang/String;
flags: ACC_STATIC, ACC_FINAL
ConstantValue: String str
4. 解析
将常量池中的符号引用(Class_info, Fieldref_info, Methodref_info)替换为直接引用(内存地址)
5. 初始化
javac 会从上到下合并类中 static 变量赋值、static 语句块,生成类构造器()V,在初始化阶段执行,此方法的执行由 JVM 保证线程安全;注意 JVM 规定有且仅有的,会立即触发对类初始化的六种 case
public class ClassX {
static {
println(“main class ClassX init”); // 1. main() 所在的主类,总是先被初始化
}
public static void main(String[] args) throws Exception {
// 首次会触发类的初始化
// SubX b = new SubX(); // new 对象 // 2. new, getsatic, putstatic, invokestatic 指令
// println(SuperX.a); // 读写类的 static 变量,或调用 static 方法
// println(SubX.c); // 3. 子类初始化,会触发父类初始化
// println(SubX.a); // 子类访问父类的静态变量,只会触发父类初始化
// 不会触发类的初始化
// println(SubX.b); // 1. 访问类的静态常量(基本类型、字符串字面量)
// println(SubX.class); // 2. 访问类对象
// println(new SubX[2]); // 3. 创建类的数组
}
}
class SuperX {
static int a = 0;
static {
println(“class SuperX initiated”);
}
}
class SubX extends SuperX {
final static double b = 0.1;
static boolean c = false;
static {
println(“class SubX initiated”);
}
}
30、类加载器
层级关系

双亲委派机制
-
原理:一个类加载器收到加载某个类的请求时,会先委派上层的父类加载器去加载,逐层向上,当父类加载器逐层向下反馈都无法加载此类后,该类加载器才会尝试自己加载;此模型保证了,诸如 rt.jar 中的
java.lang.Object类,不论在底层哪种类加载器中都一定是被 Bootstrap 类加载器加载, JVM 中仅此一份,保证了一致性 -
另外,如果您正在学习Spring Cloud,推荐一个连载多年还在继续更新的免费教程:https://blog.didispace.com/spring-cloud-learning/
-
实现
// java/lang/ClassLoader
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
synchronized (getClassLoadingLock(name)) {
// 1. 先检查自己的加载器是否已加载此类
Class<?> c = findLoadedClass(name);
if (c == null) {
long t0 = System.nanoTime();
try {
if (parent != null) {
// 2. 还有上层则委派给上层去加载
c = parent.loadClass(name, false);
} else {
// 3. 如果没有上级,则委派给 Bootstrap 加载
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
// 类不存在
}
if (c == null) {
// 4. 到自己的 classpath 中查找类,用户自定义 ClassLoader 自定义了查找规则
long t1 = System.nanoTime();
c = findClass(name);
}
}
if (resolve) {
resolveClass©;
}
return c;
}
}
31、字节码执行引擎
32、运行时栈帧结构
public static void main(String[] args) {
int a = 1008611;
int b = ++a;
}
对应运行时栈帧结构:

-
局部变量表:大小在编译期确定,用于存放实参和局部变量,以大小为 32 bit 的变量槽为最小单位
-
long, double 类型被切分为 2 个 slot 同时读写(单线程操作,无线程安全问题)
-
类对象调用方法时,slot 0 固定为当前对象的引用,即
this隐式实参 -
变量槽有重用优化,当 pc 指令超出某个槽中的变量的作用域时,该槽会被其他变量重用
public static void main(String[] args) {
{
byte[] buf = new byte[10 * 1024 * 1024];
}
System.gc(); // buf 还在局部变量表的 slot 0 中,作为 GC Root 无法被回收
// int v = 0; // 变量 v 重用 slot 0,gc 生效
// System.gc();
-
操作数栈:最大深度在编译期确定,与局部变量表配合入栈、执行指令、出栈来执行字节码指令
-
返回地址:遇到
return族指令则正常调用完成,发生异常但异常表中没有对应的 handler 则异常调用完成;正常退出到上层方法后,若有返回值则压入栈,并将程序计数器恢复到方法调用指令的下一条指令
33、方法调用
34、虚方法、非虚方法
非虚方法:编译期可知(程序运行前就唯一确定)、且运行期不可变的方法,在类加载阶段就会将方法的符号引用解析为直接引用。有 5 种:
-
静态方法(与类唯一关联):
invokestatic调用 -
私有方法(外部不可访问)、构造器方法、父类方法:
invokespecial调用 -
final 方法(无法被继承):由
invokevirtual调用(历史原因)
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数Java工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。


既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,真正体系化!
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!
如果你觉得这些内容对你有帮助,可以扫码获取!!(备注Java获取)
总结
至此,文章终于到了尾声。总结一下,我们谈论了简历制作过程中需要注意的以下三个部分,并分别给出了一些建议:
- 技术能力:先写岗位所需能力,再写加分能力,不要写无关能力;
- 项目经历:只写明星项目,描述遵循 STAR 法则;
- 简历印象:简历遵循三大原则:清晰,简短,必要,要有的放矢,不要海投;
以及最后为大家准备的福利时间:简历模板+Java面试题+热门技术系列教程视频



《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!
,并将程序计数器恢复到方法调用指令的下一条指令
33、方法调用
34、虚方法、非虚方法
非虚方法:编译期可知(程序运行前就唯一确定)、且运行期不可变的方法,在类加载阶段就会将方法的符号引用解析为直接引用。有 5 种:
-
静态方法(与类唯一关联):
invokestatic调用 -
私有方法(外部不可访问)、构造器方法、父类方法:
invokespecial调用 -
final 方法(无法被继承):由
invokevirtual调用(历史原因)
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数Java工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。[外链图片转存中…(img-xH7OPD9W-1712742888587)]
[外链图片转存中…(img-SDz9kceB-1712742888587)]
[外链图片转存中…(img-OrmSVidk-1712742888588)]
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,真正体系化!
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!
如果你觉得这些内容对你有帮助,可以扫码获取!!(备注Java获取)
总结
至此,文章终于到了尾声。总结一下,我们谈论了简历制作过程中需要注意的以下三个部分,并分别给出了一些建议:
- 技术能力:先写岗位所需能力,再写加分能力,不要写无关能力;
- 项目经历:只写明星项目,描述遵循 STAR 法则;
- 简历印象:简历遵循三大原则:清晰,简短,必要,要有的放矢,不要海投;
以及最后为大家准备的福利时间:简历模板+Java面试题+热门技术系列教程视频
[外链图片转存中…(img-r4YrTnIP-1712742888588)]
[外链图片转存中…(img-4G72LP16-1712742888588)]
[外链图片转存中…(img-ubCkQBxc-1712742888588)]
《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!
989

被折叠的 条评论
为什么被折叠?



