类的加载与ClassLoader理解——初级版本

类的加载与ClassLoader理解——初级版本

在这里插入图片描述
方法区是一个特殊的堆,包含了所有的class的数据结构(类变量,常量池,类型信息等等)
在这里插入图片描述

类的加载过程

在这里插入图片描述

  • 加载:将class(.classs)文件字节码内容加载到内存中,并将这些静态数据转换成方法区的运行时数据结构,
    然后生成一个代表这个类的java.lang.Class对象。每个类只有一个Class对象!我们无法主动创建Class对象,只能获取。
  • 链接:将Java类的二进制代码合并到JVM的运行状态之中的过程。
    • 验证:确保加载的类信息符合JVM规范,没有安全方面的问题
    • 准备:正式为类变量(static)分配内存并设置变量默认初始值阶段
    • 解析:虚拟机常量池内的符号引用(常量名)替换为直接引用(地址)的过程
  • 初始化:
    • 执行类构造器<clint>()方法的过程。类构造器<clint>()方法是由编译期自动收集类中所有类变量的赋值动作和静态代码块中的语句合并产生的。(类构造器是构造类信息的,不是构造该类对象的构造器) 对于static修饰的方法和变量的赋值语句进行合并!
    • 当初始化一个类的时候,如果发现其父类还没被初始化,则需要先触发其父类的初始化。
    • 虚拟机会保证一个类的<clint>()方法在多线程环境下被正确加锁同步。

在这里插入图片描述
类加载过程详细

public class Test02 {
    //main方法-对类的主动引用
    public static void main(String[] args) {
        //new对象-对类的主动引用
        new A();

        //读取静态变量-对类的主动引用
        System.out.println(A.m);
    }
}
class A{
    static {
        System.out.println("A类静态代码块");
        m=300;
    }

    static int m = 100;
}

/*
过程详解:
1.加载:读取Test02.class以及A.class,将字节码以二进制字节流写入到内存,
将class文件的储存结构转化为jvm方法区的数据结构,在jvm的堆中生成代表这个类的Class对象
2.链接:验证 准备:为静态变量开辟内存,并赋初始值
解析:虚拟机常量池内的符号引用(常量名)替换为直接引用(地址)的过程 String str = "a";String str1 = "a";
str-> "a"的地址 , str1->"a"的地址  str.equals(str1) = true 在解析阶段就已经决定。
3.初始化条件 main方法(程序入口)、new 对象、 读取/修改静态变量、执行静态方法、反射
<clinit>()类构造器;静态变量赋值以及静态代码块合并在一起,顺序和源文件顺序一样。
调用父类的<clinit>()类构造器
 */

什么时候发生类的初始化

public class Test03 {
    static {
        //虚拟机启动,先初始化main方法所在的类
        System.out.println("Test03初始化");
    }
    public static void main(String[] args) throws ClassNotFoundException {

        //主动引用

        //new对象 先加载父类在加载子类
        new Son();

        //调用反射包的方法
        Class.forName("reflection.Son");

        //调用类的静态方法
        int m1 = Son.m;


        //被动引用

        //子类访问父类的静态变量
        int m2 = Son.n;

        //通过数组定义类引用
        Son[] sons = new Son[5];

        //引用常量
        int m3 = Father.M;
    }
}

class Son extends Father{
    static {
        System.out.println("子类初始化");
        m=300;
    }

    static int m = 100;
}

class Father{
    static {
        System.out.println("父类初始化");
    }
    static int n  = 10;
    static final int M = 1;
}

类的主动引用(一定会发生类的初始化)

  • 当虚拟机启动,先初始化main方法所在的类
  • new一个类的对象
  • 调用类的静态成员(除了final常量)和静态方法
  • 使用java.lang.reflection包的方法对类进行反射调用
  • 当初始化一个类的时候,如果发现其父类还没被初始化,则需要先触发其父类的初始化

类的被动调用(不会发生类的初始化)

  • 当访问一个静态域时,只有真正声明这个域的类才会被初始化。如:当通过子类引用父类的静态变量,不会导致子类的初始化
  • 通过数组定义类引用,不会触发类的初始化
  • 引用常量不会触发此类的初始化(常量在链接阶段就存入方法区中类的常量池了)

类加载器的作用

  1. 类加载的作用:将class文件字节码以二进制字节流的方式加载到内存,并将静态数据转换成方法区的运行时数据结构,然后在堆中生成一个代表这个类的Class对象,
    作为方法区中类数据的访问入口。
  2. 类缓存:标志的javaSE类加载器可以按要求查找类,但一旦某个类被加载到类加载器中,它将维持加载(缓存)一段时间。
    不过jvm垃圾回收机制(GC)可以回收这些Class对象。

在这里插入图片描述

类加载器

在这里插入图片描述
从文档中对ClassLoader类的介绍可以总结出这个类的作用就是根据一个指定的类的全限定名,找到对应的Class字节码文件,然后加载它转化成一个java.lang.Class类的一个实例。
大部分java程序会使用以下3中系统提供的类加载器:

  • 启动类加载器(BootStrap ClassLoader)
    这个类加载器负责将\lib目录下的类库加载到虚拟机内存中,用来加载java的核心库,此类加载器并不继承于java.lang.ClassLoader,不能被java程序直接调用,代码是使用C++编写的.是虚拟机自身的一部分。
  • 扩展类加载器(Extendsion ClassLoader):
    这个类加载器负责加载\lib\ext目录下的类库,用来加载java的扩展库,开发者可以直接使用这个类加载器。
  • 应用程序类加载器(Application ClassLoader):
    这个类加载器负责加载用户类路径(CLASSPATH)下的类库,一般我们编写的java类都是由这个类加载器加载,这个类加载器是CLassLoader中的getSystemClassLoader()方法的返回值,所以也称为系统类加载器.一般情况下这就是系统默认的类加载器。

除此之外,我们还可以加入自己定义的类加载器,以满足特殊的需求,需要继承java.lang.ClassLoader类。
类加载器之间的层次关系如下图:
在这里插入图片描述

获取类加载器

public class Test04 {
    public static void main(String[] args) {
        //获取系统类加载器
        ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader();
        //获取扩展类加载器
        ClassLoader extClassloader = systemClassLoader.getParent();
        //获取启动类加载器
        ClassLoader rootClassLoader = extClassloader.getParent();

        System.out.println(systemClassLoader);
        System.out.println(extClassloader);
        System.out.println(rootClassLoader);
    }
}

在这里插入图片描述
根加载器即启动类加载器是由C/C++编写的,是jvm的一部分,无法由java程序直接获取,所有获取值为null;

双亲委派模型

当AppClassLoader加载一个class时,它首先不会自己去尝试加载这个类,而是把类加载请求委派给父类加载器ExtClassLoader去完成。
当ExtClassLoader加载一个class时,它首先也不会自己去尝试加载这个类,而是把类加载请求委派给BootStrapClassLoader去完成。
如果BootStrapClassLoader加载失败(例如在$JAVA_HOME/jre/lib里未查找到该class),会使用ExtClassLoader来尝试加载;
若ExtClassLoader也加载失败,则会使用AppClassLoader来加载,如果AppClassLoader也加载失败,则会报出异常ClassNotFoundException。

作用:双亲委派是为了安全而设计的,假如我们自定义了一个java.lang.Integer类如下,当使用它时,因为双亲委派,会先使用BootStrapClassLoader来进行加载,这样加载的便是jdk的Integer类,而不是自定义的这个,避免因为加载自定义核心类而造成JVM运行错误。因此,CLassLoader的加载检查是自底向上,加载是自顶向下的。

后语

正在学习jvm的知识,如果有错误会及时更正~

猜你喜欢

转载自blog.csdn.net/weixin_42812754/article/details/106282280