类的加载机制 - morris131/morris-book GitHub Wiki

类的加载机制

类的加载过程

一个java文件从被加载到被卸载这个生命过程,总共要经历5个阶段,JVM将类加载过程分为:

加载->链接(验证+准备+解析)->初始化(使用前的准备)->使用->卸载

  1. 加载:首先通过一个类的全限定名来获取此类的二进制字节流;其次将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构;最后在java堆中生成一个代表这个类的Class对象,作为方法区这些数据的访问入口。总的来说就是查找并加载类的二进制数据。

  2. 链接:

    1. 验证:确保被加载类的正确性;
    2. 准备:为类的静态变量分配内存,并将其初始化为默认值;
    3. 解析:把类中的符号引用转换为直接引用;
  3. 初始化:为类的静态变量赋予正确的初始值

类的初始化

类在以下几种情况下才会被加载和初始化。

  1. 创建类的实例,也就是new一个对象
  2. 访问某个类或接口的静态变量,或者对该静态变量赋值
  3. 调用类的静态方法
  4. 反射(Class.forName(“com.lyj.load”))
  5. 初始化一个类的子类(会首先初始化子类的父类)
  6. 调用main方法

类的初始化顺序如下:

  1. 如果这个类还没有被加载和链接,那先进行加载和链接
  2. 假如这个类存在直接父类,并且这个类还没有被初始化(注意:在一个类加载器中,类只能初始化一次),那就初始化直接的父类(不适用于接口)
  3. 加入类中存在初始化语句(如static变量和static块),那就依次执行这些初始化语句。
  4. 总的来说,初始化顺序依次是:(静态变量、静态初始化块)–>(变量、初始化块)–> 构造器;如果有父类,则顺序是:父类static方法 –> 子类static方法 –> 父类构造方法- -> 子类构造方法

类的加载

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

类的加载的最终产品是位于堆区中的Class对象。Class对象封装了类在方法区内的数据结构,并且向Java程序员提供了访问方法区内的数据结构的接口。加载类的方式有以下几种:

  1. 从本地系统直接加载
  2. 通过网络下载.class文件
  3. 从zip,jar等归档文件中加载.class文件
  4. 从专有数据库中提取.class文件
  5. 将Java源文件动态编译为.class文件(服务器)

加载器

JVM的类加载是通过ClassLoader及其子类来完成的,加载器有以下四种:

  1. BootstrapClassLoader(启动类加载器):负责加载$JAVA_HOME中jre/lib/rt.jar里所有的class,加载System.getProperty(“sun.boot.class.path”)所指定的路径或jar。
  2. ExtensionClassLoader(标准扩展类加载器):负责加载java平台中扩展功能的一些jar包,包括$JAVA_HOME中jre/lib/*.jar或-Djava.ext.dirs指定目录下的jar包。载System.getProperty(“java.ext.dirs”)所指定的路径或jar。
  3. AppClassLoader(系统类加载器):负责记载classpath中指定的jar包及目录中class 4)CustomClassLoader(自定义加载器):属于应用程序根据自身需要自定义的ClassLoader,如tomcat、jboss都会根据j2ee规范自行实现。

双亲委派模型:如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器去完成,每一个层次的类加载器都是如此,因此所有的加载请求最终都应该传送到顶层的启动类加载器中,只有当父加载器反馈自己无法完全这个加载请求时,子加载器才会尝试自己去加载。保证此类只所有ClassLoader加载一次。

面试题

package com.morris.jvm.load;

public class StaticTest {

    public static int k = 0;

    public static StaticTest t1 = new StaticTest("t1"); 

    public static StaticTest t2 = new StaticTest("t2"); 

    public static int i = print("i");

    public static int n = 99;

    public int j = print("j");
     
    {
        print("构造块");
    }

    static{
        print("静态块");
    }

    public StaticTest(String str) {
        System.out.println((++k) + ":" + str + " i=" + i + " n=" + n);
        ++n;
        ++i;
    }

    public static int print(String str) {
        System.out.println((++k) + ":" + str + " i=" + i + " n=" + n);
        ++i;
        return ++n;
    }
    
    public static void main(String[] args) {
        new StaticTest("init");
        
    }
 
}

运行结果如下:

1:j i=0 n=0
2:构造块 i=1 n=1
3:t1 i=2 n=2
4:j i=3 n=3
5:构造块 i=4 n=4
6:t2 i=5 n=5
7:i i=6 n=6
8:静态块 i=7 n=99
9:j i=8 n=100
10:构造块 i=9 n=101
11:init i=10 n=102
⚠️ **GitHub.com Fallback** ⚠️