个人笔记,深入理解 JVM,很全(1)

| -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 虚拟机规范》

39082e0da435de5ee1ff4a4cd8eb338b.png

一个 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、类加载过程

a3e47a3fd70b1b1ddd7c50ad18aa9d8a.png

1. 加载

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

1683f680ccd49587323e4f6872e69a57.png

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、类加载器

层级关系

4ce224d8bb77826e5fc491cd534c5264.png

双亲委派机制

  • 原理:一个类加载器收到加载某个类的请求时,会先委派上层的父类加载器去加载,逐层向上,当父类加载器逐层向下反馈都无法加载此类后,该类加载器才会尝试自己加载;此模型保证了,诸如 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;

}

对应运行时栈帧结构:

c8f2ea248a005524b520f8203c112d97.png

  • 局部变量表:大小在编译期确定,用于存放实参和局部变量,以大小为 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开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注Java获取)

img

总结

至此,文章终于到了尾声。总结一下,我们谈论了简历制作过程中需要注意的以下三个部分,并分别给出了一些建议:

  1. 技术能力:先写岗位所需能力,再写加分能力,不要写无关能力;
  2. 项目经历:只写明星项目,描述遵循 STAR 法则;
  3. 简历印象:简历遵循三大原则:清晰,简短,必要,要有的放矢,不要海投;

以及最后为大家准备的福利时间:简历模板+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获取)

img

总结

至此,文章终于到了尾声。总结一下,我们谈论了简历制作过程中需要注意的以下三个部分,并分别给出了一些建议:

  1. 技术能力:先写岗位所需能力,再写加分能力,不要写无关能力;
  2. 项目经历:只写明星项目,描述遵循 STAR 法则;
  3. 简历印象:简历遵循三大原则:清晰,简短,必要,要有的放矢,不要海投;

以及最后为大家准备的福利时间:简历模板+Java面试题+热门技术系列教程视频

[外链图片转存中…(img-r4YrTnIP-1712742888588)]

[外链图片转存中…(img-4G72LP16-1712742888588)]

[外链图片转存中…(img-ubCkQBxc-1712742888588)]

《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值