2)图解方法执行流程
代码
public class Demo3_1 {
public static void main ( String[ ] args) {
int a = 10 ;
int b = Short. MAX_VALUE + 1 ;
int c = a + b;
System. out. println ( c) ;
}
}
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
常量池载入运行时常量池 常量池也属于方法区,只不过这里单独提出来了 方法字节码载入方法区 (stack=2,locals=4) 对应操作数栈有 2 个空间(每个空间 4 个字节),局部变量表中有 4 个槽位。 执行引擎开始执行字节码 bipush 10
将一个 byte 压入操作数栈 (其长度会补齐 4 个字节),类似的指令还有
sipush 将一个 short 压入操作数栈(其长度会补齐 4 个字节) ldc 将一个 int 压入操作数栈 ldc2_w 将一个 long 压入操作数栈(分两次压入 ,因为 long 是 8 个字节) 这里小的数字都是和字节码指令存在一起,超过 short 范围的数字存入了常量池
istore 1 将操作数栈栈顶元素弹出,放入局部变量表的 slot 1 中 对应代码中的 a = 10 ldc #3 读取运行时常量池中 #3 ,即 32768 (超过 short 最大值范围的数会被放到运行时常量池中),将其加载到操作数栈中 注意 Short.MAX_VALUE 是 32767,所以 32768 = Short.MAX_VALUE + 1 实际是在编译期间计算好的。 istore 2 将操作数栈中的元素弹出,放到局部变量表的 2 号位置 iload1 iload2 将局部变量表中 1 号位置和 2 号位置的元素放入操作数栈中。因为只能在操作数栈中执行运算操作 iadd 将操作数栈中的两个元素弹出栈并相加,结果在压入操作数栈中。 istore 3 将操作数栈中的元素弹出,放入局部变量表的3号位置。 getstatic #4 在运行时常量池中找到 #4 ,发现是一个对象,在堆内存中找到该对象,并将其引用放入操作数栈中 iload 3 将局部变量表中 3 号位置的元素压入操作数栈中。 invokevirtual #5 找到常量池 #5 项,定位到方法区 java/io/PrintStream.println:(I)V 方法 生成新的栈帧(分配 locals、stack等) 传递参数,执行新栈帧中的字节码 执行完毕,弹出栈帧 清除 main 操作数栈内容 return 完成 main 方法调用,弹出 main 栈帧,程序结束
3)通过字节码指令分析问题
代码
public class Code_11_ByteCodeTest {
public static void main ( String[ ] args) {
int i = 0 ;
int x = 0 ;
while ( i < 10 ) {
x = x++ ;
i++ ;
}
System. out. println ( x) ;
}
}
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
为什么最终的 x 结果为 0 呢? 通过分析字节码指令即可知晓
Code:
stack= 2 , locals= 3 , args_size= 1
0 : iconst_0
1 : istore_1
2 : iconst_0
3 : istore_2
4 : iload_1
5 : bipush 10
7 : if_icmpge 21
10 : iload_2
11 : iinc 2 , 1
14 : istore_2
15 : iinc 1 , 1
18 : goto 4
21 : getstatic #2
24 : iload_2
25 : invokevirtual #3
28 : return
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
class="hide-preCode-box">1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
4)构造方法
cinit()V
public class Code_12_CinitTest {
static int i = 10 ;
static {
i = 20 ;
}
static {
i = 30 ;
}
public static void main ( String[ ] args) {
System. out. println ( i) ;
}
}
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
编译器会按从上至下的顺序,收集所有 static 静态代码块和静态成员赋值的代码,合并为一个特殊的方法 cinit()V :
stack= 1 , locals= 0 , args_size= 0
0 : bipush 10
2 : putstatic #3
5 : bipush 20
7 : putstatic #3
10 : bipush 30
12 : putstatic #3
15 : return
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
init()V
public class Code_13_InitTest {
private String a = "s1" ;
{
b = 20 ;
}
private int b = 10 ;
{
a = "s2" ;
}
public Code_13_InitTest ( String a, int b) {
this . a = a;
this . b = b;
}
public static void main ( String[ ] args) {
Code_13_InitTest d = new Code_13_InitTest ( "s3" , 30 ) ;
System. out. println ( d. a) ;
System. out. println ( d. b) ;
}
}
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
class="hide-preCode-box">1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
编译器会按从上至下的顺序,收集所有 {} 代码块和成员变量赋值的代码,形成新的构造方法,但原始构造方法内的代码总是在后.
Code:
stack= 2 , locals= 3 , args_size= 3
0 : aload_0
1 : invokespecial #1
4 : aload_0
5 : ldc #2
7 : putfield #3
10 : aload_0
11 : bipush 20
13 : putfield #4
16 : aload_0
17 : bipush 10
19 : putfield #4
22 : aload_0
23 : ldc #5
25 : putfield #3
28 : aload_0
29 : aload_1
30 : putfield #3
33 : aload_0
34 : iload_2
35 : putfield #4
38 : return
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
class="hide-preCode-box">1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
5)方法调用
public class Code_14_MethodTest {
public Code_14_MethodTest ( ) {
}
private void test1 ( ) {
}
private final void test2 ( ) {
}
public void test3 ( ) {
}
public static void test4 ( ) {
}
public static void main ( String[ ] args) {
Code_14_MethodTest obj = new Code_14_MethodTest ( ) ;
obj. test1 ( ) ;
obj. test2 ( ) ;
obj. test3 ( ) ;
Code_14_MethodTest. test4 ( ) ;
}
}
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
class="hide-preCode-box">1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
不同方法在调用时,对应的虚拟机 指令有所区别
私有、构造、被 final 修饰的方法,在调用时都使用 invokespecial 指令 普通成员方法在调用时,使用 invokevirtual 指令。因为编译期间无法确定该方法的内容,只有在运行期间才能确定 静态方法在调用时使用 invokestatic 指令
Code:
stack= 2 , locals= 2 , args_size= 1
0 : new #2
3 : dup
4 : invokespecial #3
7 : astore_1
8 : aload_1
9 : invokespecial #4
12 : aload_1
13 : invokespecial #5
16 : aload_1
17 : invokevirtual #6
20 : invokestatic #7
23 : return
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
new 是创建【对象】,给对象分配堆内存,执行成功会将【对象引用】压入操作数栈 dup 是赋值操作数栈栈顶的内容,本例即为【对象引用】,为什么需要两份引用呢,一个是要配合 invokespecial 调用该对象的构造方法 “init”: ()V (会消耗掉栈顶一个引用),另一个要 配合 astore_1 赋值给局部变量 终方法(final),私有方法(private),构造方法都是由 invokespecial 指令来调用,属于静态绑定 普通成员方法是由 invokevirtual 调用,属于动态绑定,即支持多态 成员方法与静态方法调用的另一个区别是,执行方法前是否需要【对象引用】
6)多态 原理
因为普通成员方法需要在运行时才能确定具体的内容,所以虚拟机需要调用 invokevirtual 指令 在执行 invokevirtual 指令时,经历了以下几个步骤
先通过栈帧中对象的引用找到对象 分析对象头,找到对象实际的 Class Class 结构中有 vtable 查询 vtable 找到方法的具体地址 执行方法的字节码
7)异常处理
try-catch
public class Code_15_TryCatchTest {
public static void main ( String[ ] args) {
int i = 0 ;
try {
i = 10 ;
} catch ( Exception e) {
i = 20 ;
}
}
}
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
对应字节码指令
Code:
stack= 1 , locals= 3 , args_size= 1
0 : iconst_0
1 : istore_1
2 : bipush 10
4 : istore_1
5 : goto 12
8 : astore_2
9 : bipush 20
11 : istore_1
12 : return
Exception table:
from to target type
2 5 8 Class java / lang/ Exception
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
可以看到多出来一个 Exception table 的结构,[from, to) 是前闭后开(也就是检测 2~4 行)的检测范围,一旦这个范围内的字节码执行出现异常,则通过 type 匹配异常类型,如果一致,进入 target 所指示行号 8 行的字节码指令 astore_2 是将异常对象引用存入局部变量表的 2 号位置(为 e )
多个 single-catch
public class Code_16_MultipleCatchTest {
public static void main ( String[ ] args) {
int i = 0 ;
try {
i = 10 ;
} catch ( ArithmeticException e) {
i = 20 ;
} catch ( Exception e) {
i = 30 ;
}
}
}
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
对应的字节码
Code:
stack= 1 , locals= 3 , args_size= 1
0 : iconst_0
1 : istore_1
2 : bipush 10
4 : istore_1
5 : goto 19
8 : astore_2
9 : bipush 20
11 : istore_1
12 : goto 19
15 : astore_2
16 : bipush 30
18 : istore_1
19 : return
Exception table:
from to target type
2 5 8 Class java / lang/ ArithmeticException
2 5 15 Class java / lang/ Exception
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
class="hide-preCode-box">1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
因为异常出现时,只能进入 Exception table 中一个分支,所以局部变量表 slot 2 位置被共用
finally
public class Code_17_FinallyTest {
public static void main ( String[ ] args) {
int i = 0 ;
try {
i = 10 ;
} catch ( Exception e) {
i = 20 ;
} finally {
i = 30 ;
}
}
}
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
对应字节码
Code:
stack= 1 , locals= 4 , args_size= 1
0 : iconst_0
1 : istore_1
2 : bipush 10
4 : istore_1
5 : bipush 30
7 : istore_1
8 : goto 27
11 : astore_2
12 : bipush 20
14 : istore_1
15 : bipush 30
17 : istore_1
18 : goto 27
21 : astore_3
22 : bipush 30
24 : istore_1
25 : aload_3
26 : athrow
27 : return
Exception table:
from to target type
2 5 11 Class java / lang/ Exception
2 5 21 any
11 15 21 any
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
class="hide-preCode-box">1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
可以看到 finally 中的代码被复制了 3 份,分别放入 try 流程,catch 流程以及 catch 剩余的异常类型流程 注意:虽然从字节码指令看来,每个块中都有 finally 块,但是 finally 块中的代码只会被执行一次
finally 中的 return
public class Code_18_FinallyReturnTest {
public static void main ( String[ ] args) {
int i = Code_18_FinallyReturnTest. test ( ) ;
System. out. println ( i) ;
}
public static int test ( ) {
int i;
try {
i = 10 ;
return i;
} finally {
i = 20 ;
return i;
}
}
}
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
class="hide-preCode-box">1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
对应字节码
Code:
stack= 1 , locals= 3 , args_size= 0
0 : bipush 10
2 : istore_0
3 : iload_0
4 : istore_1
5 : bipush 20
7 : istore_0
8 : iload_0
9 : ireturn
10 : astore_2
11 : bipush 20
13 : istore_0
14 : iload_0
15 : ireturn
Exception table:
from to target type
0 5 10 any
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
class="hide-preCode-box">1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
由于 finally 中的 ireturn 被插入了所有可能的流程,因此返回结果肯定以finally的为准 至于字节码中第 2 行,似乎没啥用,且留个伏笔,看下个例子 跟上例中的 finally 相比,发现没有 athrow 了,这告诉我们:如果在 finally 中出现了 return,会吞掉异常 所以不要在finally中进行返回操作
被吞掉的异常
public static int test ( ) {
int i;
try {
i = 10 ;
i = i/ 0 ;
return i;
} finally {
i = 20 ;
return i;
}
}
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
会发现打印结果为 20 ,并未抛出异常
finally 不带 return
public static int test ( ) {
int i = 10 ;
try {
return i;
} finally {
i = 20 ;
}
}
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
对应字节码
Code:
stack= 1 , locals= 3 , args_size= 0
0 : bipush 10
2 : istore_0
3 : iload_0
4 : istore_1
5 : bipush 20
7 : istore_0
8 : iload_1
9 : ireturn
10 : astore_2
11 : bipush 20
13 : istore_0
14 : aload_2
15 : athrow
Exception table:
from to target type
3 5 10 any
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
class="hide-preCode-box">1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
8)Synchronized
public class Code_19_SyncTest {
public static void main ( String[ ] args) {
Object lock = new Object ( ) ;
synchronized ( lock) {
System. out. println ( "ok" ) ;
}
}
}
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
对应字节码
Code:
stack= 2 , locals= 4 , args_size= 1
0 : new #2
3 : dup
4 : invokespecial #1
7 : astore_1
8 : aload_1
9 : dup
10 : astore_2
11 : monitorenter
12 : getstatic #3
15 : ldc #4
17 : invokevirtual #5
20 : aload_2
21 : monitorexit
22 : goto 30
25 : astore_3
26 : aload_2
27 : monitorexit
28 : aload_3
29 : athrow
30 : return
Exception table:
from to target type
12 22 25 any
25 28 25 any
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
class="hide-preCode-box">1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
3、编译期处理
所谓的 语法糖 ,其实就是指 java 编译器把 .java 源码编译为 .class 字节码的过程中,自动生成和转换的一些代码,主要是为了减轻程序员的负担,算是 java 编译器给我们的一个额外福利 注意 ,以下代码的分析,借助了 javap 工具,idea 的反编译功能,idea 插件 jclasslib 等工具。另外, 编译器转换的结果直接就是 class 字节码 ,只是为了便于阅读,给出了 几乎等价 的 java 源码方式,并不是编译器还会转换出中间的 java 源码,切记。
1)默认构造器
public class Candy1 {
}
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
经过编译期优化后
public class Candy1 {
public Candy1 ( ) {
super ( ) ;
}
}
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
2)自动拆装箱
基本类型和其包装类型的相互转换过程,称为拆装箱 在 JDK 5 以后,它们的转换可以在编译期自动完成
public class Candy2 {
public static void main ( String[ ] args) {
Integer x = 1 ;
int y = x;
}
}
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
转换过程如下
public class Candy2 {
public static void main ( String[ ] args) {
Integer x = Integer. valueOf ( 1 ) ;
int y = x. intValue ( ) ;
}
}
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
3)泛型集合取值
泛型也是在 JDK 5 开始加入的特性,但 java 在编译泛型代码后会执行泛型擦除的动作,即泛型信息在编译为字节码之后就丢失了,实际的类型都当做了 Object 类型来处理:
public class Candy3 {
public static void main ( String[ ] args) {
List< Integer> list = new ArrayList < > ( ) ;
list. add ( 10 ) ;
Integer x = list. get ( 0 ) ;
}
}
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
对应字节码
Code:
stack= 2 , locals= 3 , args_size= 1
0 : new #2
3 : dup
4 : invokespecial #3
7 : astore_1
8 : aload_1
9 : bipush 10
11 : invokestatic #4
14 : invokeinterface #5 , 2
19 : pop
20 : aload_1
21 : iconst_0
22 : invokeinterface #6 , 2
27 : checkcast #7
30 : astore_2
31 : return
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
class="hide-preCode-box">1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
所以调用 get 函数取值时,有一个类型转换的操作。
Integer x = ( Integer) list. get ( 0 ) ;
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
如果要将返回结果赋值给一个 int 类型的变量,则还有自动拆箱的操作
int x = ( Integer) list. get ( 0 ) . intValue ( ) ;
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
使用反射可以得到,参数的类型以及泛型类型。泛型反射代码如下:
public static void main ( String[ ] args) throws NoSuchMethodException {
Method method = Code_20_ReflectTest. class . getMethod ( "test" , List. class , Map. class ) ;
Type[ ] types = method. getGenericParameterTypes ( ) ;
for ( Type type : types) {
if ( type instanceof ParameterizedType ) {
ParameterizedType parameterizedType = ( ParameterizedType) type;
System. out. println ( "原始类型 - " + parameterizedType. getRawType ( ) ) ;
Type[ ] arguments = parameterizedType. getActualTypeArguments ( ) ;
for ( int i = 0 ; i < arguments. length; i++ ) {
System. out. printf ( "泛型参数[%d] - %s\n" , i, arguments[ i] ) ;
}
}
}
}
public Set< Integer> test ( List< String> list, Map< Integer, Object> map) {
return null;
}
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
class="hide-preCode-box">1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
输出:
原始类型 - interface java. util. List
泛型参数[ 0 ] - class java. lang. String
原始类型 - interface java. util. Map
泛型参数[ 0 ] - class java. lang. Integer
泛型参数[ 1 ] - class java. lang. Object
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
4)可变参数
可变参数也是 JDK 5 开始加入的新特性: 例如:
public class Candy4 {
public static void foo ( String. . . args) {
String[ ] arr = args;
System. out. println ( arr. length) ;
}
public static void main ( String[ ] args) {
foo ( "hello" , "world" ) ;
}
}
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
可变参数 String… args 其实是一个 String[] args ,从代码中的赋值语句中就可以看出来。 同 样 java 编译器会在编译期间将上述代码变换为:
public class Candy4 {
public Candy4 { }
public static void foo ( String[ ] args) {
String[ ] arr = args;
System. out. println ( arr. length) ;
}
public static void main ( String[ ] args) {
foo ( new String [ ] ) ;
}
}
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
注意,如果调用的是 foo() ,即未传递参数时,等价代码为 foo(new String[]{}) ,创建了一个空数组,而不是直接传递的 null .
5)foreach 循环
仍是 JDK 5 开始引入的语法糖,数组的循环:
public class Candy5 {
public static void main ( String[ ] args) {
int [ ] arr = { 1 , 2 , 3 , 4 , 5 } ;
for ( int x : arr) {
System. out. println ( x) ;
}
}
}
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
编译器会帮我们转换为
public class Candy5 {
public Candy5 ( ) { }
public static void main ( String[ ] args) {
int [ ] arr = new int [ ] { 1 , 2 , 3 , 4 , 5 } ;
for ( int i = 0 ; i < arr. length; ++ i) {
int x = arr[ i] ;
System. out. println ( x) ;
}
}
}
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
如果是集合使用 foreach
public class Candy5 {
public static void main ( String[ ] args) {
List< Integer> list = Arrays. asList ( 1 , 2 , 3 , 4 , 5 ) ;
for ( Integer x : list) {
System. out. println ( x) ;
}
}
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
集合要使用 foreach ,需要该集合类实现了 Iterable 接口,因为集合的遍历需要用到迭代器 Iterator.
public class Candy5 {
public Candy5 ( ) { }
public static void main ( String[ ] args) {
List< Integer> list = Arrays. asList ( 1 , 2 , 3 , 4 , 5 ) ;
Iterator< Integer> iterator = list. iterator ( ) ;
while ( iterator. hasNext ( ) ) {
Integer x = iterator. next ( ) ;
System. out. println ( x) ;
}
}
}
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
6)switch 字符串
从 JDK 7 开始,switch 可以作用于字符串和枚举类,这个功能其实也是语法糖,例如:
public class Cnady6 {
public static void main ( String[ ] args) {
String str = "hello" ;
switch ( str) {
case "hello" :
System. out. println ( "h" ) ;
break ;
case "world" :
System. out. println ( "w" ) ;
break ;
default :
break ;
}
}
}
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
在编译器中执行的操作
public class Candy6 {
public Candy6 ( ) {
}
public static void main ( String[ ] args) {
String str = "hello" ;
int x = - 1 ;
switch ( str. hashCode ( ) ) {
case 99162322 :
if ( str. equals ( "hello" ) ) {
x = 0 ;
}
break ;
case 11331880 :
if ( str. equals ( "world" ) ) {
x = 1 ;
}
break ;
default :
break ;
}
switch ( x) {
case 0 :
System. out. println ( "h" ) ;
break ;
case 1 :
System. out. println ( "w" ) ;
break ;
default :
break ;
}
}
}
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
class="hide-preCode-box">1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
过程说明:
在编译期间,单个的 switch 被分为了两个
第一个用来匹配字符串,并给 x 赋值
字符串的匹配用到了字符串的 hashCode ,还用到了 equals 方法 使用 hashCode 是为了提高比较效率,使用 equals 是防止有 hashCode 冲突(如 BM 和 C .) 第二个用来根据x的值来决定输出语句
7)switch 枚举
enum SEX {
MALE, FEMALE;
}
public class Candy7 {
public static void main ( String[ ] args) {
SEX sex = SEX. MALE;
switch ( sex) {
case MALE:
System. out. println ( "man" ) ;
break ;
case FEMALE:
System. out. println ( "woman" ) ;
break ;
default :
break ;
}
}
}
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
class="hide-preCode-box">1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
编译器中执行的代码如下
enum SEX {
MALE, FEMALE;
}
public class Candy7 {
static class $MAP {
static int [ ] map = new int [ 2 ] ;
static {
map[ SEX. MALE. ordinal ( ) ] = 1 ;
map[ SEX. FEMALE. ordinal ( ) ] = 2 ;
}
}
public static void main ( String[ ] args) {
SEX sex = SEX. MALE;
int x = $MAP. map[ sex. ordinal ( ) ] ;
switch ( x) {
case 1 :
System. out. println ( "man" ) ;
break ;
case 2 :
System. out. println ( "woman" ) ;
break ;
default :
break ;
}
}
}
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
class="hide-preCode-box">1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
8)枚举类
JDK 7 新增了枚举类,以前面的性别枚举为例:
enum SEX {
MALE, FEMALE;
}
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
转换后的代码
public final class Sex extends Enum < Sex> {
public static final Sex MALE;
public static final Sex FEMALE;
private static final Sex[ ] $VALUES;
static {
MALE = new Sex ( "MALE" , 0 ) ;
FEMALE = new Sex ( "FEMALE" , 1 ) ;
$VALUES = new Sex [ ] { MALE, FEMALE} ;
}
private Sex ( String name, int ordinal) {
super ( name, ordinal) ;
}
public static Sex[ ] values ( ) {
return $VALUES. clone ( ) ;
}
public static Sex valueOf ( String name) {
return Enum. valueOf ( Sex. class , name) ;
}
}
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
class="hide-preCode-box">1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
9)try-with-resources
JDK 7 开始新增了对需要关闭的资源处理的特殊语法,‘try-with-resources’
try ( 资源变量 = 创建资源对象) {
} catch ( ) {
}
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
其中资源对象需要实现 AutoCloseable 接口,例如 InputStream 、 OutputStream 、 Connection 、 Statement 、 ResultSet 等接口都实现了 AutoCloseable ,使用 try-with- resources 可以不用写 finally 语句块,编译器会帮助生成关闭资源代码,例如:
public class Candy9 {
public static void main ( String[ ] args) {
try ( InputStream is = new FileInputStream ( "d:\\1.txt" ) ) {
System. out. println ( is) ;
} catch ( IOException e) {
e. printStackTrace ( ) ;
}
}
}
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
会被转换为:
public class Candy9 {
public Candy9 ( ) { }
public static void main ( String[ ] args) {
try {
InputStream is = new FileInputStream ( "d:\\1.txt" ) ;
Throwable t = null;
try {
System. out. println ( is) ;
} catch ( Throwable e1) {
t = e1;
throw e1;
} finally {
if ( is != null) {
if ( t != null) {
try {
is. close ( ) ;
} catch ( Throwable e2) {
t. addSuppressed ( e2) ;
}
} else {
is. close ( ) ;
}
}
}
} catch ( IOException e) {
e. printStackTrace ( ) ;
}
}
}
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
class="hide-preCode-box">1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
为什么要设计一个 addSuppressed(Throwable e) (添加被压制异常)的方法呢?是为了防止异常信息的丢失(想想 try-with-resources 生成的 fianlly 中如果抛出了异常):
public class Test6 {
public static void main ( String[ ] args) {
try ( MyResource resource = new MyResource ( ) ) {
int i = 1 / 0 ;
} catch ( Exception e) {
e. printStackTrace ( ) ;
}
}
}
class MyResource implements AutoCloseable {
public void close ( ) throws Exception {
throw new Exception ( "close 异常" ) ;
}
}
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
输出:
java. lang. ArithmeticException: / by zero
at test. Test6. main ( Test6. java: 7 )
Suppressed: java. lang. Exception: close 异常
at test. MyResource. close ( Test6. java: 18 )
at test. Test6. main ( Test6. java: 6 )
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
10)方法重写时的桥接方法
我们都知道,方法重写时对返回值分两种情况: - 父子类的返回值完全一致 - 子类返回值可以是父类返回值的子类(比较绕口,见下面的例子)
class A {
public Number m ( ) {
return 1 ;
}
}
class B extends A {
@Override
public Integer m ( ) {
return 2 ;
}
}
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
对于子类,java 编译器会做如下处理:
class B extends A {
public Integer m ( ) {
return 2 ;
}
public synthetic bridge Number m ( ) {
return m ( ) ;
}
}
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
其中桥接方法比较特殊,仅对 java 虚拟机可见,并且与原来的 public Integer m() 没有命名冲突,可以 用下面反射代码来验证:
public static void main ( String[ ] args) {
for ( Method m : B. class . getDeclaredMethods ( ) ) {
System. out. println ( m) ;
}
}
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
结果:
public java. lang. Integer cn. ali. jvm. test. B. m ( )
public java. lang. Number cn. ali. jvm. test. B. m ( )
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
11)匿名内部类
public class Candy10 {
public static void main ( String[ ] args) {
Runnable runnable = new Runnable ( ) {
@Override
public void run ( ) {
System. out. println ( "running..." ) ;
}
} ;
}
}
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
转换后的代码
public class Candy10 {
public static void main ( String[ ] args) {
Runnable runnable = new Candy10 $1 ( ) ;
}
}
final class Candy10 $1 implements Runnable {
public Demo8$1 ( ) { }
@Override
public void run ( ) {
System. out. println ( "running..." ) ;
}
}
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
class="hide-preCode-box">
引用局部变量的匿名内部类,源代码:
public class Candy11 {
public static void test ( final int x) {
Runnable runnable = new Runnable ( ) {
@Override
public void run ( ) {
System. out. println ( "ok:" + x) ;
}
} ;
}
}
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
转换后代码:
final class Candy11 $1 implements Runnable {
int val$x;
Candy11$1 ( int x) {
this . val$x = x;
}
public void run ( ) {
System. out. println ( "ok:" + this . val$x) ;
}
}
public class Candy11 {
public static void test ( final int x) {
Runnable runnable = new Candy11 $1 ( x) ;
}
}
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
class="hide-preCode-box">
注意:这同时解释了为什么匿名内部类引用局部变量时,局部变量必须是 final 的:因为在创建 Candy11$1 对象时,将 x 的值赋值给了 Candy11$1 对象的 值后,如果不是 final 声明的 x 值发生了改变,匿名内部类则值不一致。
4、类加载阶段
1)加载
将类的字节码载入方法区(1.8后为元空间,在本地内存中)中,内部采用 C++ 的 instanceKlass 描述 java 类,它的重要 field 有:
_java_mirror 即 java 的类镜像,例如对 String 来说,它的镜像类就是 String.class,作用是把 klass 暴露给 java 使用 _super 即父类 _fields 即成员变量 _methods 即方法 _constants 即常量池 _class_loader 即类加载器 _vtable 虚方法表 _itable 接口方法 如果这个类还有父类没有加载,先加载父类 加载和链接可能是交替运行的 instanceKlass保存在方法区。JDK 8以后,方法区位于元空间中,而元空间又位于本地内存中 _java_mirror则是保存在堆内存中 InstanceKlass和*.class(JAVA镜像类)互相保存了对方的地址 类的对象在对象头中保存了*.class的地址。让对象可以通过其找到方法区中的instanceKlass,从而获取类的各种信息
注意
instanceKlass 这样的【元数据】是存储在方法区(1.8 后的元空间内),但 _java_mirror 是存储在堆中 可以通过前面介绍的 HSDB 工具查看
2)连接
验证 验证类是否符合 JVM规范,安全性检查 用 UE 等支持二进制的编辑器修改 HelloWorld.class 的魔数,在控制台运行 准备 为 static 变量分配空间,设置默认值
static 变量在 JDK 7 之前存储于 instanceKlass 末尾,从 JDK 7 开始,存储于 _java_mirror 末尾 static 变量分配空间和赋值是两个步骤,分配空间在准备阶段完成,赋值在初始化阶段完成 如果 static 变量是 final 的基本类型,以及字符串常量,那么编译阶段值就确定了,赋值在准备阶段完成 如果 static 变量是 final 的,但属于引用类型,那么赋值也会在初始化阶段完成将常量池中的符号引用解析为直接引用
public class Code_22_AnalysisTest {
public static void main ( String[ ] args) throws ClassNotFoundException, IOException {
ClassLoader classLoader = Code_22_AnalysisTest. class . getClassLoader ( ) ;
Class< ? > c = classLoader. loadClass ( "cn.ali.jvm.test.C" ) ;
System. in. read ( ) ;
}
}
class C {
D d = new D ( ) ;
}
class D {
}
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
class="hide-preCode-box">1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
3)初始化
()v 方法
初始化即调用 ()V ,虚拟机会保证这个类的『构造方法』的线程安全
发生的时机
概括得说,类初始化是【懒惰的】
main 方法所在的类,总会被首先初始化 首次访问这个类的静态变量或静态方法时 子类初始化,如果父类还没初始化,会引发 子类访问父类的静态变量,只会触发父类的初始化 Class.forName new 会导致初始化
不会导致类初始化的情况
访问类的 static final 静态常量(基本类型和字符串)不会触发初始化 类对象.class 不会触发初始化 创建该类的数组不会触发初始化
public class Load1 {
static {
System. out. println ( "main init" ) ;
}
public static void main ( String[ ] args) throws ClassNotFoundException {
}
}
class A {
static int a = 0 ;
static {
System. out. println ( "a init" ) ;
}
}
class B extends A {
final static double b = 5.0 ;
static boolean c = false ;
static {
System. out. println ( "b init" ) ;
}
}
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
class="hide-preCode-box">1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45
4)练习
从字节码分析,使用 a,b,c 这三个常量是否会导致 E 初始化
public class Load2 {
public static void main ( String[ ] args) {
System. out. println ( E. a) ;
System. out. println ( E. b) ;
System. out. println ( E. c) ;
}
}
class E {
public static final int a = 10 ;
public static final String b = "hello" ;
public static final Integer c = 20 ;
static {
System. out. println ( "E cinit" ) ;
}
}
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
class="hide-preCode-box">1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
典型应用 - 完成懒惰初始化单例模式
public class Singleton {
private Singleton ( ) { }
private static class LazyHolder {
static final Singleton INSTANCE = new Singleton ( ) ;
}
public static Singleton getInstance ( ) {
return LazyHolder. INSTANCE;
}
}
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
以上的实现特点是:
5、类加载器
类加载器虽然只用于实现类的加载动作,但它在Java程序中起到的作用却远超类加载阶段 对于任意一个类,都必须由加载它的类加载器和这个类本身一起共同确立其在 Java 虚拟机中的唯一性,每一个类加载器,都拥有一个独立的类名称空间。这句话可以表达得更通俗一些:比较两个类是否“相等”,只有在这两个类是由同一个类加载器加载的前提下才有意义,否则,即使这两个类来源于同一个 Class 文件,被同一个 Java 虚拟机加载,只要加载它们的类加载器不同,那这两个类就必定不相等! 以JDK 8为例
class="table-box">名称 加载的类 说明 Bootstrap ClassLoader(启动类加载器) JAVA_HOME/jre/lib 无法直接访问 Extension ClassLoader(拓展类加载器) JAVA_HOME/jre/lib/ext 上级为Bootstrap,显示为null Application ClassLoader(应用程序类加载器) classpath 上级为Extension 自定义类加载器 自定义 上级为Application
1)启动类的加载器
可通过在控制台输入指令,使得类被启动类加器加载
2)扩展类的加载器
如果 classpath 和 JAVA_HOME/jre/lib/ext 下有同名类,加载时会使用拓展类加载器加载。当应用程序类加载器发现拓展类加载器已将该同名类加载过了,则不会再次加载。
3)双亲委派模式
双亲委派模式,即调用类加载器ClassLoader 的 loadClass 方法时,查找类的规则。 loadClass源码
protected Class< ? > loadClass ( String name, boolean resolve)
throws ClassNotFoundException
{
synchronized ( getClassLoadingLock ( name) ) {
Class< ? > c = findLoadedClass ( name) ;
if ( c == null) {
long t0 = System. nanoTime ( ) ;
try {
if ( parent != null) {
c = parent. loadClass ( name, false ) ;
} else {
c = findBootstrapClassOrNull ( name) ;
}
} catch ( ClassNotFoundException e) {
}
if ( c == null) {
long t1 = System. nanoTime ( ) ;
c = findClass ( name) ;
sun. misc. PerfCounter. getParentDelegationTime ( ) . addTime ( t1 - t0) ;
sun. misc. PerfCounter. getFindClassTime ( ) . addElapsedTimeFrom ( t1) ;
sun. misc. PerfCounter. getFindClasses ( ) . increment ( ) ;
}
}
if ( resolve) {
resolveClass ( c) ;
}
return c;
}
}
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
class="hide-preCode-box">1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41
4)自定义类加载器
使用场景
想加载非 classpath 随意路径中的类文件 通过接口来使用实现,希望解耦时,常用在框架设计 这些类希望予以隔离,不同应用的同名类都可以加载,不冲突,常见于 tomcat 容器
步骤
继承 ClassLoader 父类 要遵从双亲委派机制,重写 findClass 方法 不是重写 loadClass 方法,否则不会走双亲委派机制 读取类文件的字节码 调用父类的 defineClass 方法来加载类 使用者调用该类加载器的 loadClass 方法
破坏双亲委派模式
双亲委派模型的第一次“被破坏”其实发生在双亲委派模型出现之前——即JDK1.2面世以前的“远古”时代
建议用户重写findClass()方法,在类加载器中的loadClass()方法中也会调用该方法 双亲委派模型的第二次“被破坏”是由这个模型自身的缺陷导致的
如果有基础类型又要调用回用户的代码,此时也会破坏双亲委派模式 双亲委派模型的第三次“被破坏”是由于用户对程序动态性的追求而导致的
这里所说的“动态性”指的是一些非常“热”门的名词:代码热替换(Hot Swap)、模块热部署(Hot Deployment)等
6、运行期优化
1)即时编译
分层编译 JVM 将执行状态分成了 5 个层次:
0层:解释执行,用解释器将字节码翻译为机器码 1层:使用 C1 即时编译器编译执行(不带 profiling) 2层:使用 C1 即时编译器编译执行(带基本的profiling) 3层:使用 C1 即时编译器编译执行(带完全的profiling) 4层:使用 C2 即时编译器编译执行
profiling 是指在运行过程中收集一些程序执行状态的数据,例如【方法的调用次数】,【循环的 回边次数】等
即时编译器(JIT)与解释器的区别
解释器
将字节码解释为机器码,下次即使遇到相同的字节码,仍会执行重复的解释 是将字节码解释为针对所有平台都通用的机器码 即时编译器
将一些字节码编译为机器码,并存入 Code Cache,下次遇到相同的代码,直接执行,无需再编译 根据平台类型,生成平台特定的机器码
对于大部分的不常用的代码,我们无需耗费时间将其编译成机器码,而是采取解释执行的方式运行;另一方面,对于仅占据小部分的热点代码,我们则可以将其编译成机器码,以达到理想的运行速度。 执行效率上简单比较一下 Interpreter < C1 < C2,总的目标是发现热点代码(hotspot名称的由 来),并优化这些热点代码。 逃逸分析 逃逸分析(Escape Analysis)简单来讲就是,Java Hotspot 虚拟机可以分析新创建对象的使用范围,并决定是否在 Java 堆上分配内存的一项技术
逃逸分析的 JVM 参数如下:
开启逃逸分析:-XX:+DoEscapeAnalysis 关闭逃逸分析:-XX:-DoEscapeAnalysis 显示分析结果:-XX:+PrintEscapeAnalysis
逃逸分析技术在 Java SE 6u23+ 开始支持,并默认设置为启用状态,可以不用额外加这个参数
对象逃逸状态
全局逃逸(GlobalEscape)
即一个对象的作用范围逃出了当前方法或者当前线程,有以下几种场景:
对象是一个静态变量 对象是一个已经发生逃逸的对象 对象作为当前方法的返回值
参数逃逸(ArgEscape)
即一个对象被作为方法参数传递或者被参数引用,但在调用过程中不会发生全局逃逸,这个状态是通过被调方法的字节码确定的
没有逃逸
逃逸分析优化 针对上面第三点,当一个对象没有逃逸时,可以得到以下几个虚拟机的优化
锁消除 我们知道线程同步锁是非常牺牲性能的,当编译器确定当前对象只有当前线程使用,那么就会移除该对象的同步锁 例如,StringBuffer 和 Vector 都是用 synchronized 修饰线程安全的,但大部分情况下,它们都只是在当前线程中用到,这样编译器就会优化移除掉这些锁操作 锁消除的 JVM 参数如下:
开启锁消除:-XX:+EliminateLocks 关闭锁消除:-XX:-EliminateLocks
锁消除在 JDK8 中都是默认开启的,并且锁消除都要建立在逃逸分析的基础上
标量替换 首先要明白标量和聚合量,基础类型和对象的引用可以理解为标量,它们不能被进一步分解。而能被进一步分解的量就是聚合量,比如:对象 对象是聚合量,它又可以被进一步分解成标量,将其成员变量分解为分散的变量,这就叫做标量替换。
这样,如果一个对象没有发生逃逸,那压根就不用创建它,只会在栈或者寄存器上创建它用到的成员标量,节省了内存空间,也提升了应用程序性能 标量替换的 JVM 参数如下:
开启标量替换:-XX:+EliminateAllocations 关闭标量替换:-XX:-EliminateAllocations 显示标量替换详情:-XX:+PrintEliminateAllocations
标量替换同样在 JDK8 中都是默认开启的,并且都要建立在逃逸分析的基础上
栈上分配 当对象没有发生逃逸时,该对象就可以通过标量替换分解成成员标量分配在栈内存中,和方法的生命周期一致,随着栈帧出栈时销毁,减少了 GC 压力,提高了应用程序性能
方法内联 内联函数 内联函数就是在程序编译时,编译器将程序中出现的内联函数的调用表达式用内联函数的函数体来直接进行替换
JVM内联函数 C++ 是否为内联函数由自己决定,Java 由编译器决定。Java 不支持直接声明为内联函数的,如果想让他内联,你只能够向编译器提出请求: 关键字 final 修饰 用来指明那个函数是希望被 JVM 内联的,如
public final void doSomething ( ) {
}
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
总的来说,一般的函数都不会被当做内联函数,只有声明了final后,编译器才会考虑是不是要把你的函数变成内联函数
JVM内建有许多运行时优化。首先短方法更利于JVM推断。流程更明显,作用域更短,副作用也更明显。如果是长方法JVM可能直接就跪了。
第二个原因则更重要:方法内联
如果JVM监测到一些小方法被频繁的执行,它会把方法的调用替换成方法体本身,如:
private int add4 ( int x1, int x2, int x3, int x4) {
return add2 ( x1, x2) + add2 ( x3, x4) ;
}
private int add2 ( int x1, int x2) {
return x1 + x2;
}
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
方法调用被替换后
private int add4 ( int x1, int x2, int x3, int x4) {
return x1 + x2 + x3 + x4;
}
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
2)反射优化
public class Reflect1 {
public static void foo ( ) {
System. out. println ( "foo..." ) ;
}
public static void main ( String[ ] args) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
Method foo = Demo3. class . getMethod ( "foo" ) ;
for ( int i = 0 ; i<= 16 ; i++ ) {
foo. invoke ( null) ;
}
}
}
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
foo.invoke 前面 0 ~ 15 次调用使用的是 MethodAccessor 的 NativeMethodAccessorImpl 实现 invoke 方法源码
@CallerSensitive
public Object invoke ( Object obj, Object. . . args)
throws IllegalAccessException, IllegalArgumentException,
InvocationTargetException
{
if ( ! override) {
if ( ! Reflection. quickCheckMemberAccess ( clazz, modifiers) ) {
Class< ? > caller = Reflection. getCallerClass ( ) ;
checkAccess ( caller, clazz, obj, modifiers) ;
}
}
MethodAccessor ma = methodAccessor;
if ( ma == null) {
ma = acquireMethodAccessor ( ) ;
}
return ma. invoke ( obj, args) ;
}
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
class="hide-preCode-box">1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
会由 DelegatingMehodAccessorImpl 去调用 NativeMethodAccessorImpl NativeMethodAccessorImpl 源码
class NativeMethodAccessorImpl extends MethodAccessorImpl {
private final Method method;
private DelegatingMethodAccessorImpl parent;
private int numInvocations;
NativeMethodAccessorImpl ( Method var1) {
this . method = var1;
}
public Object invoke ( Object var1, Object[ ] var2) throws IllegalArgumentException, InvocationTargetException {
if ( ++ this . numInvocations > ReflectionFactory. inflationThreshold ( ) && ! ReflectUtil. isVMAnonymousClass ( this . method. getDeclaringClass ( ) ) ) {
MethodAccessorImpl var3 = ( MethodAccessorImpl) ( new MethodAccessorGenerator ( ) ) . generateMethod ( this . method. getDeclaringClass ( ) , this . method. getName ( ) , this . method. getParameterTypes ( ) , this . method. getReturnType ( ) , this . method. getExceptionTypes ( ) , this . method. getModifiers ( ) ) ;
this . parent. setDelegate ( var3) ;
}
return invoke0 ( this . method, var1, var2) ;
}
void setParent ( DelegatingMethodAccessorImpl var1) {
this . parent = var1;
}
private static native Object invoke0 ( Method var0, Object var1, Object[ ] var2) ;
}
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
class="hide-preCode-box">1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
private static int inflationThreshold = 15 ;
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
一开始if条件不满足,就会调用本地方法 invoke0 随着 numInvocation 的增大,当它大于 ReflectionFactory.inflationThreshold 的值 16 时,就会本地方法访问器替换为一个运行时动态生成的访问器,来提高效率
这时会从反射调用变为正常调用,即直接调用 Reflect1.foo()
阿里开源工具:arthas -boot
五、内存模型
参考这篇文章!
data-report-view="{"mod":"1585297308_001","spm":"1001.2101.3001.6548","dest":"https://blog.csdn.net/weixin_50280576/article/details/113784268","extend1":"pc","ab":"new"}">>
评论记录:
回复评论: