JVM_类的加载、连接与初始化(一)

本文深入探讨Java类加载过程,包括加载、连接、初始化三个阶段,解析类加载器的工作原理,以及类的主动使用和被动使用的区别。同时,介绍了如何使用JVM参数追踪类加载信息。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

加载:查找并加载类的二进制数据。

当编译和连接一个C++程序时,所获得的可执行二进制文件只能在指定的硬件平台和操作系统上运行,因为这个二进制文件包含了对目标处理器的机器语言。而Java编译器把Java源文件的指令翻译成字节码,这种字节码就是Java虚拟机的“机器语言”。与普通程序不同的是,Java程序(class文件)并不是本地的可执行程序。当运行Java程序时,首先运行JVM(Java虚拟机),然后再把Java class加载到JVM里头运行,负责加载Java class的这部分就叫做Class Loader。

类的加载指的是将类的.class文件中的二进制数据读入到内存中,将其放在运行时数据区的方法区内,然后在内存中创建一个java.lang.Class对象(HotSpot虚拟机将其放在了方法区中)用来封装类在方法区内的数据结构。

加载.class文件的方式

*从本地系统中直接加载

*通过网络下载.class文件

*从zip,jar等归档文件中加载.class文件

*从专有数据库中提取.class文件

*将Java源文件动态编译为.class文件(jsp)

连接: 

     1. 验证:确保被加载的类的正确性。

Java编译器翻译后生成的字节码即class文件还是可以被替换、删除和编辑的,此时JVM还会对加载的类再次验证。

     2.准备:为类的静态变量分配内存,并将其初始化为默认值。

       

如以上代码,JVM在准备之初,先为a进行初始化,即a的值为0。

     3.解析:把类中的符号引用转换为直接应用。

 在刚加载好一个类的时候,Class文件里的常量池和每个方法的字节码(Code属性)会被基本原样的拷贝到内存里先放着,也就是说仍然处于使用“符号引用”的状态;直到真的要被使用到的时候才会被解析(resolve)为直接引用。

参考一篇R大的文章,现在我是没搞看懂https://www.zhihu.com/question/30300585/answer/51335493

初始化:为类的静态变量赋予正确的初始值。 

最后为初始化,创建一个内存空间存放1,a就指向该该内存的地址。 (刚刚接触JVM,正在学习中)

 Java程序对类的使用方式可分类

 所有的Java虚拟机实现必须在每个类或接口被Java程序“首次主动使用”时才初始化他们。

1.主动使用

 *创建类的实例

*访问某个类的接口变量,或者对该静态变量赋值

*调用类的静态方法

*反射

*初始化一个类的子类

*java虚拟机启动时被表明为启动类的类 

*JDK1.7开始提供的动态语言的支持

public class Test {
    public static void main(String[] args){
        System.out.println(Child.str);
    }
}
class Parent{
    public static String str = "hello world";
    static {
        System.out.println("Parent class");
    }
}
class Child extends Parent{
    public static String str2 = "hello child";

    static {
        System.out.println("Child class");
    }
}

执行结果:
Parent class
hello world

 对于静态来说,只有直接定义了该字段的类才会被初始化。以上代虽然执行时是Child.str,但是并没用主动使用Child,而是初始化了父类Parent,谁定义了静态变量就是主动使用那个类。

public class Test {
    public static void main(String[] args){
        System.out.println(Child.str2);
    }
}
class Parent{
    public static String str = "hello world";
    static {
        System.out.println("Parent class");
    }
}
class Child extends Parent{
    public static String str2 = "hello child";

    static {
        System.out.println("Child class");
    }
}

执行结果:
Parent class
Child class
hello child

以上代码执行了Child.str2,str2是在Child中被定义的,所以主动使用了Child,而Parent先要被初始化,因为Child类是Parent的子类,一个子类被初始化,他的所有父类都要被初始化完毕

使用-XX:TraceClassLoading 用于追踪类的加载信息并打印出来

配置完成后执行下代码

public class Test {
    public static void main(String[] args){
        System.out.println(Child.str);
    }
}
class Parent{
    public static String str = "hello world";
    static {
        System.out.println("Parent class");
    }
}
class Child extends Parent{
    public static String str2 = "hello child";

    static {
        System.out.println("Child class");
    }
}

 执行后结果如下,打印了很多加载信息出来,最先加载的就是Object类,然后看最底下。

[Opened C:\java8\jdk1.8.0_144\jre\lib\rt.jar]
[Loaded java.lang.Object from C:\java8\jdk1.8.0_144\jre\lib\rt.jar]
[Loaded java.io.Serializable from C:\java8\jdk1.8.0_144\jre\lib\rt.jar]
[Loaded java.lang.Comparable from C:\java8\jdk1.8.0_144\jre\lib\rt.jar]
[Loaded java.lang.CharSequence from C:\java8\jdk1.8.0_144\jre\lib\rt.jar]
..........................忽略中间部分...................................
[Loaded Test from file:/D:/workspace-for-yun/untitled/out/production/untitled/]//启动类
[Loaded sun.launcher.LauncherHelper$FXHelper from C:\java8\jdk1.8.0_144\jre\lib\rt.jar]
[Loaded java.lang.Class$MethodArray from C:\java8\jdk1.8.0_144\jre\lib\rt.jar]
[Loaded java.lang.Void from C:\java8\jdk1.8.0_144\jre\lib\rt.jar]
[Loaded Parent from file:/D:/workspace-for-yun/untitled/out/production/untitled/]
[Loaded Child from file:/D:/workspace-for-yun/untitled/out/production/untitled/]
Parent class
hello world
[Loaded java.lang.Shutdown from C:\java8\jdk1.8.0_144\jre\lib\rt.jar]
[Loaded java.lang.Shutdown$Lock from C:\java8\jdk1.8.0_144\jre\lib\rt.jar]

 从以上执行结果可以看出,Child类没有被初始化,但是被加载了。

2.被动使用 

除了以上其中,其他使用Java类都是“被动使用”,即不会主动初始化,但是不代表不会加载。

 JVM参数

JVM提供了很多参数供开发者使用,一部分参数是默认关闭的,一部分是默认开启的,所以就有+/-,+表示开启,-表示关闭。可以去Oracle官网了解更多参数。

-XX:+<option>,表示开启option选项。

-XX:-<option>,表示关闭option选项。

-XX:<option>=<value>,表示将option选项值设置为value。

 关于常量池

先看一个代码,看看会输出什么。

public class Test2 {
    public static void main(String[] args){
        System.out.println(Parent2.str);
    }
}

class Parent2{
    public static final String str = "parent2 class";
    static {
        System.out.println("parent2 static block");
    }
}

是 parent2 static block \n parent2 class?

正确答案:parent2 class

被final定义的叫做常量。

*常量在编译阶段会存入到调用这个常量的方法所在的类的常量中。

*本质上,调用类并没有直接应用到定义常量的类,因此不会触发。

*注意:这里指的是将常量存放到Test2类中的常量池中,之后Test2和Parent2没有半毛钱关系了。

做个实验,把Parent2.class文件删除看下可不可以打印出来。

结果是正常打印出来了。

class类反编译

进入控制台敲命令行

javap -c Test2.class

Compiled from "Test2.java"
public class Test2 {
  public Test2();//构造方法
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: return

  public static void main(java.lang.String[]);
    Code:
       0: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
       3: ldc           #4                  // String parent2 class
       5: invokevirtual #5                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
       8: return
}

Code下面的都是助记符,getstatic、ldc、invokevirtual等。

ldc:表示将int、float、String等常量值从常量池中推至栈顶。

bipush:表示将单字节(-128-127)的常量推送至栈顶。

sipush:表示短整形的常量值(-32768-32767)的常量推至栈顶。

iconst_1:表示将一个int类型1推至栈顶(0-5)或者boolean的true。

iconst_0:表示将一个int类型0推至栈顶 或者boolean的false。

iconst_m1:表示int类型-1推至栈顶。

查看更多助记符

自己写代码反编译看看。

public class Test2 {
    public static void main(String[] args){
        System.out.println(Parent2.str);
        System.out.println(Parent2.i);
        System.out.println(Parent2.a);
        System.out.println(Parent2.a_6);
        System.out.println(Parent2.a_5);
        System.out.println(Parent2.a_4);
        System.out.println(Parent2.a_3);
        System.out.println(Parent2.a_2);
        System.out.println(Parent2.a_1);
        System.out.println(Parent2.a0);
        System.out.println(Parent2.a1);
        System.out.println(Parent2.a2);
        System.out.println(Parent2.a5);
        System.out.println(Parent2.a6);
        System.out.println(Parent2.b);
        System.out.println(Parent2.c);
        System.out.println(Parent2.d);
        System.out.println(Parent2.f);
    }
}

class Parent2{
    public static final String str = "parent2 class";

    public static final short i = 7;

    public static final int a = 10;

    public static final int a_6 = -6;

    public static final int a_5 = -5;

    public static final int a_4 = -4;

    public static final int a_3 = -3;

    public static final int a_2 = -2;

    public static final int a_1 = -1;

    public static final int a0 = 0;

    public static final int a1 = 1;

    public static final int a2 = 2;

    public static final int a5 = 5;

    public static final int a6 = 6;

    public static final long b = 1222222;

    public static final double c = 1222222.01;

    public static final boolean d = true;

    public static final boolean f = false;

    static {
        System.out.println("parent2 static block");
    }
}
public class Test2 {
  public Test2();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: return

  public static void main(java.lang.String[]);
    Code:
       0: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
       3: ldc           #4                  // String parent2 class
       5: invokevirtual #5                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
       8: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
      11: bipush        7
      13: invokevirtual #6                  // Method java/io/PrintStream.println:(I)V
      16: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
      19: bipush        10
      21: invokevirtual #6                  // Method java/io/PrintStream.println:(I)V
      24: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
      27: bipush        -6
      29: invokevirtual #6                  // Method java/io/PrintStream.println:(I)V
      32: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
      35: bipush        -5
      37: invokevirtual #6                  // Method java/io/PrintStream.println:(I)V
      40: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
      43: bipush        -4
      45: invokevirtual #6                  // Method java/io/PrintStream.println:(I)V
      48: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
      51: bipush        -3
      53: invokevirtual #6                  // Method java/io/PrintStream.println:(I)V
      56: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
      59: bipush        -2
      61: invokevirtual #6                  // Method java/io/PrintStream.println:(I)V
      64: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
      67: iconst_m1
      68: invokevirtual #6                  // Method java/io/PrintStream.println:(I)V
      71: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
      74: iconst_0
      75: invokevirtual #6                  // Method java/io/PrintStream.println:(I)V
      78: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
      81: iconst_1
      82: invokevirtual #6                  // Method java/io/PrintStream.println:(I)V
      85: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
      88: iconst_2
      89: invokevirtual #6                  // Method java/io/PrintStream.println:(I)V
      92: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
      95: iconst_5
      96: invokevirtual #6                  // Method java/io/PrintStream.println:(I)V
      99: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
     102: bipush        6
     104: invokevirtual #6                  // Method java/io/PrintStream.println:(I)V
     107: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
     110: ldc2_w        #7                  // long 1222222l
     113: invokevirtual #9                  // Method java/io/PrintStream.println:(J)V
     116: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
     119: ldc2_w        #10                 // double 1222222.01d
     122: invokevirtual #12                 // Method java/io/PrintStream.println:(D)V
     125: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
     128: iconst_1
     129: invokevirtual #13                 // Method java/io/PrintStream.println:(Z)V
     132: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
     135: iconst_0
     136: invokevirtual #13                 // Method java/io/PrintStream.println:(Z)V
     139: return
}

可以查找相关的助记符的类进行查阅。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值