java类加载器

class字节码生成与反编译工具

字节码操作
javassist
asm

javac与javap

1
2
3
javac -encoding utf-8 Foo.java

javap -c Foo.class

asmtools

1.准备asmtools.jar

1
2
3
4
5
6
下载安装ant https://ant.apache.org/bindownload.cgi

git clone -b 7.0-b09 --depth=1 https://gitee.com/mirrors_openjdk/asmtools.git

cd asmtools/build
ant

2. 准备文件Foo.java

1
2
3
4
5
6
7
8
9
public class Foo {

public static void main(String[] args) {
boolean boolValue = true; // 将这个true替换为2或者3,再看看打印结果
if (boolValue) System.out.println("Hello, Java!");
if (boolValue == true) System.out.println("Hello, JVM!");
}

}

3. javac编译为class

1
2
3
javac Foo.java 

生成Foo.class

4. 查看class内容

class文件是二进制文件,无法直接查看;需要反编译为jasm或java文件才行

Foo.jasm

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
$ java -jar asmtools.jar jdis Foo.class

super public class Foo
version 52:0
{
public Method "<init>":"()V"
stack 1 locals 1
{
aload_0;
invokespecial Method java/lang/Object."<init>":"()V";
return;
}
public static Method main:"([Ljava/lang/String;)V"
stack 2 locals 2
{
iconst_1;
istore_1;
iload_1;
ifeq L14;
getstatic Field java/lang/System.out:"Ljava/io/PrintStream;";
ldc String "Hello, Java!";
invokevirtual Method java/io/PrintStream.println:"(Ljava/lang/String;)V";
L14: stack_frame_type append;
locals_map int;
iload_1;
iconst_1;
if_icmpne L27;
getstatic Field java/lang/System.out:"Ljava/io/PrintStream;";
ldc String "Hello, JVM!";
invokevirtual Method java/io/PrintStream.println:"(Ljava/lang/String;)V";
L27: stack_frame_type same;
return;
}

} // end Class Foo

反编译生成Foo.java http://www.benf.org/other/cfr/

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
$ java -jar cfr-0.152.jar Foo.class
/*
* Decompiled with CFR 0.152.
*/
public class Foo {
public static void main(String[] stringArray) {
boolean bl = true;
if (bl) {
System.out.println("Hello, Java!");
}
if (bl) {
System.out.println("Hello, JVM!");
}
}
}

5.基于jasm生成class

1
2
3
4
5
$ java -jar asmtools.jar jasm Foo.jasm

$ java Foo
Hello, Java!
Hello, JVM!

5.1 绕过java语言规范,看看boolean类型是啥

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
将Foo.jasm第14行iconst_1改为iconst_2

$ java -jar asmtools.jar jasm Foo.jasm

lixl@DESKTOP-0SMOHUS MINGW64 /f/test
$ java Foo
Hello, Java!

lixl@DESKTOP-0SMOHUS MINGW64 /f/test
$ java -jar cfr-0.152.jar Foo.class
/*
* Decompiled with CFR 0.152.
*/
public class Foo {
public static void main(String[] stringArray) {
int n = 2;//证明java编译时将boolean处理成int(默认true=1,false=0)
if (n != 0) {
System.out.println("Hello, Java!");
}
if (n == 1) {
System.out.println("Hello, JVM!");
}
}
}

5.2 换个写法,把boolean bl改为静态变量

Foo.java

1
2
3
4
5
6
7
8
9
10
11
public class Foo {

private static boolean boolValue;

public static void main(String[] args) {
boolValue = true; // 将这个true替换为2或者3,再看看打印结果
if (boolValue) System.out.println("Hello, Java!");
if (boolValue == true) System.out.println("Hello, JVM!");
}

}

将java编译为class后查看jasm

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
lixl@DESKTOP-0SMOHUS MINGW64 /f/test
$ javac -encoding UTF-8 Foo.java

lixl@DESKTOP-0SMOHUS MINGW64 /f/test
$ java -jar asmtools.jar jdis Foo.class

super public class Foo
version 52:0
{
private static Field boolValue:Z;

public Method "<init>":"()V"
stack 1 locals 1
{
aload_0;
invokespecial Method java/lang/Object."<init>":"()V";
return;
}
public static Method main:"([Ljava/lang/String;)V"
stack 2 locals 1
{
iconst_1;
putstatic Field boolValue:"Z";
getstatic Field boolValue:"Z";
ifeq L18;
getstatic Field java/lang/System.out:"Ljava/io/PrintStream;";
ldc String "Hello, Java!";
invokevirtual Method java/io/PrintStream.println:"(Ljava/lang/String;)V";
L18: stack_frame_type same;
getstatic Field boolValue:"Z";
iconst_1;
if_icmpne L33;
getstatic Field java/lang/System.out:"Ljava/io/PrintStream;";
ldc String "Hello, JVM!";
invokevirtual Method java/io/PrintStream.println:"(Ljava/lang/String;)V";
L33: stack_frame_type same;
return;
}

} // end Class Foo

将Foo.jasm中16改为iconst_2;然后编译为class并执行

1
2
3
4
5
6
$ java -jar asmtools.jar jasm Foo.jasm

lixl@DESKTOP-0SMOHUS MINGW64 /f/test
$ java Foo

没有输出结果

将Foo.jasm中16改为iconst_3;然后编译为class并执行

1
2
3
4
5
6
7
lixl@DESKTOP-0SMOHUS MINGW64 /f/test
$ java -jar asmtools.jar jasm Foo.jasm

lixl@DESKTOP-0SMOHUS MINGW64 /f/test
$ java Foo
Hello, Java!
Hello, JVM!

将class反编译为java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
$ java -jar cfr-0.152.jar Foo.class
/*
* Decompiled with CFR 0.152.
*/
public class Foo {
private static boolean boolValue;

public static void main(String[] stringArray) {
boolValue = 3;
if (boolValue) {
System.out.println("Hello, Java!");
}
if (boolValue) {
System.out.println("Hello, JVM!");
}
}
}

总结:

1
2
3
4
1.boolean只能为true和false,只是java语言规范,
字节码层面是最为int处理的(true=1,false=0);
2.静态变量,在字节码层面已经确定类型 boolValue:"Z"
3.boolean进行计算时会做掩码操作(2==0 3==1)

class加载过程

https://blog.csdn.net/briblue/article/details/54973413

https://gitee.com/lixl/dcevm.git

https://gitee.com/lixl/HotswapAgent.git

step1 加载

  1. 就是找class文件的过程

  2. 类加载器先判断是否加载过该class;没加载过的再加载

  3. BootClassLoader(根类加载器) –> ExtClassLocadr –> AppClassLoader (父子关系、是组合而非继承)
    双亲委派,就是ClassLoader加载类时先让ParentClassLoader尝试加载,ParentClassLoader加载不到时才会尝试加载;
    好处是,同一个类(packageName+className相同)不会加载多次;

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    https://www.zhihu.com/question/466696410
    1.如何自定义类加载器(加解密class)?
    2.tomcat如何打破双亲委派?
    3.JDBC是否打破双亲委派?
    ```

    ## step2 链接
    验证-》准备-》解析
    验证:class是否符合jvm规范,确定一下是不是随便生成的二进制文件
    准备:给静态字段分配内存、虚方法绑定方法表
    解析:一般再下一步初始化的时候才做,jvm规范也没要求

    ## step3 初始化
    为标记为常量值的字段赋值,以及执行 < clinit > 方法的过程

    1.若直接赋值的静态字段被final修饰、且类型是基本类型或者String,
    则会被Java编译器标记为常量值,由jvm直接初始化.
    其余类型的初始化都会被置于 <clinit> 中,
    jvm通过加锁的方式 来保证 clinit 方法只被执行一次. (线程安全的懒加载单例)

    2.哪些做法会执行类的初始化

https://blog.csdn.net/qq_22771739/article/details/86348962
主动触发及被动触发


## 扩展:
初始化类、变量时与jvm内存空间的关系

JVM并不会直接使用.class文件,类加载链接的目的就是在JVM中创建相应的类结构,会存储在元空间(方法区)。