Java的类加载机制

首先概览一个对象的创建过程

 

1.类加载器

  类加载器的作用是将一个class文件加载到内存中,Java中的ClassLoader类是所有类加载器的父类,当一个类加载进内存之后,实际上它的二进制的内容会占用一块内存空间,于此同时生成了一个Class类的对象,这个Class对象指向了二进制内容占用那一块内存。

  (待验证)二进制内容这一块东西是在matespace里面。生成的Class对象是在堆内存中。

2.类加载器分类

  由上至下级别依次降低

    1.BootStrapClassLoader 负责加载jdk的核心类 

    2.ExtensionClassLoader 负责加载扩展包的各种类文件

    3.AppClassLoader 负责加载classpath下面的类

    4.CustomerClassLoader 可以加载任何类,看如何去定义

  可以通过一个简单的main方法去查看各个加载器的加载范围

System.out.println("----------BootStrapClassLoader-sun.boot.class.path-----------");
System.out.println(System.getProperty("sun.boot.class.path").replaceAll(":", System.lineSeparator()));
System.out.println("----------ExtensionClassLoader-java.ext.dirs-----------------");
System.out.println(System.getProperty("java.ext.dirs").replaceAll(":", System.lineSeparator()));
System.out.println("----------AppClassLoader-java.class.path---------------------");
System.out.println(System.getProperty("java.class.path").replaceAll(":", System.lineSeparator()));
查看各个ClassLoader的加载范围

3.双亲委派机制

  Classloader类中的loaderClass方法就是双亲委派的具体实现,通过一个很像递归的方式,去逐级上推的在各个加载器的Cache中寻找需要加载的Class,如果都没有找到再逐级的下传去调用子类具体实现的findClass方法来加载对应的类,最后如果还不能加载,抛出异常classNotFountException

  也就是说如果自定义一个类加载器的话,只需要继承Classloader类并重写findClass方法就可以了。双亲委派其实具体是指从子到父,再从父到子的一次循环查找/加载类的过程。这个命名方式确实很容易让人产生继承方面的误解。

  Why Use: 主要是为了安全,为了避免重复加载覆盖。

PS: 父加载器不是父类!不是继承!而是组合关系,代码里是通过定义一个parent属性,来达到拥有父加载器的效果。

  如果还是不懂就说的直白一些:加载一个类的时候首先通过customer的缓存去,没有找到,就去app的缓存中找,也没找到就去exdention的缓存,如果还没有就去bootstrap去。如果最后还没有,就会去委托bootstrap去加载,不在负责范围内,再去委托extension加载,不在负责范围内,再去委托app去加载,不在负责范围内,再去委托customer加载,最后如果还不能加载,抛出异常classNotFountException

protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
        synchronized(this.getClassLoadingLock(name)) {
            Class<?> c = this.findLoadedClass(name);
            if (c == null) {
                long t0 = System.nanoTime();

                try {
                    if (this.parent != null) {
                        c = this.parent.loadClass(name, false);
                    } else {
                        c = this.findBootstrapClassOrNull(name);
                    }
                } catch (ClassNotFoundException var10) {
                }

                if (c == null) {
                    long t1 = System.nanoTime();
                    c = this.findClass(name);
                    PerfCounter.getParentDelegationTime().addTime(t1 - t0);
                    PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
                    PerfCounter.getFindClasses().increment();
                }
            }

            if (resolve) {
                this.resolveClass(c);
            }

            return c;
        }
    }
JDK11内的loadClass实现

4.如何打破双亲委派? 什么情况下需要打破?

  只要重写loadClass就可以打破双亲委派

  jdk1.2的时候必须重写loadClass,所以是强制性的打破。

  一个类的不同版本的热部署。

5.自定义类加载器的实现 

package com.wjr.jvm;

import java.io.*;
import java.lang.reflect.InvocationTargetException;


public class J001_CustomerClassLoader extends ClassLoader{

    @Override
    public Class<?> findClass(String name){
        String filePath = "/Users/banana/myproject/MyJava/MY/jlab/src/main/java/"+name.replace(".","/")+".class";
        File file = new File(filePath);
        FileInputStream fileInputStream;
        byte[] bytes = new byte[0];
        try {
            fileInputStream = new FileInputStream(file);
            bytes = fileInputStream.readAllBytes();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return defineClass(name, bytes, 0, bytes.length);
    }

    public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
        J001_CustomerClassLoader j001_customerClassLoader = new J001_CustomerClassLoader();
        Class<?> aClass = j001_customerClassLoader.loadClass("com.wjr.jvm.Test_HelloWorld");
        Test_HelloWorld test_helloWorld = (Test_HelloWorld) aClass.getDeclaredConstructor().newInstance();
        System.out.println(j001_customerClassLoader.getParent().getParent());
        System.out.println(j001_customerClassLoader.getParent());
        System.out.println(aClass.getClassLoader());
        System.out.println(aClass);
        test_helloWorld.sayHello(System.currentTimeMillis());

    }
}

自定义类加载器代码
自定义类加载器的实现

6.类加载过程

Loading(加载) -> Linking(链接) -> Initalization(初始化)

  其中Linking分成三个小部分:

    Validation(验证) -> Preparation(准备) -> Resolution(解析)

Loading 

  将二进制文件装载进入内存

Linking

  Valifation

    校验二进制文件是否合规

  Preparation

    给Class文件的静态成员变量付默认值

  Resolution

    将一些类、方法符号引用转换成内存地址引用,比如将java/lang/Object转换成实际地址引用

Initalization

  给对象副初始值,调用静态代码块

7.半初始化问题

  半初始化状态引发的单例初始值被使用的问题

    invokespecial 和 aload指令可以重排序,导致它的半初始化,aload已经把

  比如一个DCL(Double Check Loading)的单例模式如果单例不加volatile修饰,那么由于上面的指令重排序的问题,会导致aload提前执行,其他线程会提前拿到没有完全初始化的对象,那么对象里的各种属性没有被初始化,只有默认值。这个时候就会出现问题。

 

posted @ 2021-02-03 17:56  JunSir-WJR  阅读(150)  评论(0)    收藏  举报