ie不加载jre_Java 类加载过程

类加载过程简介

类的加载过程主要包括: 加载阶段、连接阶段、初始化阶段

d7741e9aa2b788853bceb987602a17f7.png

加载阶段: 加载类的二进制数据文件(class 文件)

连接阶段:

验证: 确保类文件的正确性
准备: 类的静态文件分配内存,并且初始化默认值
解析: 类中的符号引用转换为直接引用

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

在Java 程序启动之后,可能会包含很多的类,当然并不是每一个类都不会被初始化,JVM 对类的初始化是一个延迟的机制,也就是lazy 的。

类的主动使用和被动使用

主动使用: 会使得类加载并初始化

  • new 关键字创建一个类
  • 访问类的静态变量包括读取和更新都会使得类初始化
  • 访问类的静态方法包括读取和更新都会使得类初始化
  • 对类进行反射操作的时候
  • 初始化子类导致父类的初始化
  • 当一个类中包括main 函数的时候,当这个main 函数得到启动的时候,这个类会初始化
被动使用: 不会使得类加载并初始化
  • 问类的静态常量(final 修饰的变量)不会使得类初始化
  • 构建数组的时候并不会使得类初始化
Simple[] simples = new Simple[10];

类的初始化阶段

类的初始化过程是整个类加载过程中最后一个阶段,在这个阶段做的做主要的一件事情就是执行<clinit>() 方法,在这个方法中所有的类变量都会被赋予正确的值,也就是编写的时候指定的值。这个方法中包含所有类变量的赋值动作和静态语句块的执行代码,

另外<clinit>() 方法和类的构造函数有所不同,它不需要显示的调用父类的构造器,虚拟机会保证父类的 <clinit>() 方法最先执行,因此父类的静态变量总是能够得到优先赋值。

当主动使用触发了类的初始化之后就会调用这个方法。

    static {
        try {
            System.out.println("static code will be inviked");
            TimeUnit.SECONDS.sleep(5);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
        IntStream.range(0,5).forEach(i -> new Thread(DeadLock::new));
    }

执行上面的代码片段,在同一个世界,只能有一个线程执行到静态代码块中的内容,并且静态代码块中仅仅会被执行一次,JVM 保证了 <clinit>() 方法在多线程的执行环境下的同步

类的加载器

类的加载器负责类的加载职责,任何一个对象的class 在JVM 中都只存在唯一的一份。

JVM 有三大类加载器,不同的加载器负责将不同的类加载到JVM 内存之中,并且它们之中严格遵守这父委托机制。

c6c4a2653b6a767cf671ecac08958fe7.png

Bootstrap

根加载器,为最顶层的加载器,主要负责虚拟机核心类库的加载如Java.lang库的加载,可以通过-Xbootclasspath 来指定根加载器的路径,也可以通过类属性来得知当前Jvm 的根加载器都加载类哪些资源。

System.out.println("Bootstrap" + String.class.getClassLoader());
   null
System.out.println(System.getProperty("sun.boot.class.path"));
   /Library/Java/JavaVirtualMachines/jdk1.8.0_181.jdk/Contents/Home/jre/lib/resources.jar:
   /Library/Java/JavaVirtualMachines/jdk1.8.0_181.jdk/Contents/Home/jre/lib/rt.jar

第一个获取的classloader 会是null,是因为根类加载器是获取不到引用的。

第二个输出更加在的加载路径

ExtClassLoader

扩展类加载器,这个类加载器是根加载器,它主要是加载JAVA_HOME 下的jre/lb/ext 目录里面的类库

 System.out.println(System.getProperty("java.ext.dirs"));

获取扩展类加载资源的路径

ApplicationClassLoader

系统类加载器是一种常见的类加载器,其负责加载 classpath 下的类库资源,

System.out.println(System.getProperty("java.class.path"));
System.out.println(DeadLock.class.getClassLoader());

第一个打印出当前系统类加载器资源的路径,

第二个打印出当前类的类加载器,会输出

sun.misc.Launcher$AppClassLoader@18b4aac2

自定义类加载器

当然也是可以进行自定义的类加载器的

package thread.chapt1;

public class MyClassLoader extends ClassLoader {
    private final static Path DEFAULT_CLASS_DIR = Paths.get("/Users/xuxliu/Ifoods/Java");

    // 默认的class 路径
    public MyClassLoader() {
        super();
        this.classDir = DEFAULT_CLASS_DIR;
    }

    private final Path classDir;

    // 指定父类加载器
    // 允许传入指定路径的 class 路径
    public MyClassLoader(String classDir, ClassLoader parent) {
        super(parent);
        this.classDir = Paths.get(classDir);
    }

    // 指定加载特定的目录
    public MyClassLoader(String classDir) {
        super();
        this.classDir = Paths.get(classDir);
    }

    // 重写父类的 findClass 这是非常重要的
    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        // 读取二进制数据
        byte[] classBytes = new byte[0];
        try {
            classBytes = this.readClassBytes(name);
        } catch (IOException e) {
            e.printStackTrace();
        }
        if (classBytes == null || classBytes.length == 0) {
            throw new ClassNotFoundException("can not load the class");
        }

        // 调用 defineClass 方法定义class,读取class 文件
        return this.defineClass(name, classBytes, 0, classBytes.length);
    }

    // class 文件读入内存
    private byte[] readClassBytes(String name) throws ClassNotFoundException, IOException {
        // 包名分隔符专为文件路径
        String classPath = name.replace(".", "/");

        Path classFullPath = classDir.resolve(Paths.get(classPath) + name);

        if (!classFullPath.toFile().exists()){
            throw new ClassNotFoundException("the class " + name + " not found");
        }
        try (ByteArrayOutputStream baos = new ByteArrayOutputStream()) {
            Files.copy(classFullPath, baos);
            return baos.toByteArray();
        }
    }

    @Override
    public String toString() {
        return "MyClassLoader";
    }


    public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {
        MyClassLoader myClassLoader = new MyClassLoader();
        Class<?> aClass = myClassLoader.loadClass("thread.chapt1.HelloWorld");

        System.out.println(aClass.getClassLoader());

        Object helloWorld = aClass.newInstance();

        System.out.println(helloWorld);

        Method welcome = aClass.getMethod("welcome");
        String result = (String) welcome.invoke(helloWorld);

        System.out.println(result);
    }
}

上面的代码先对程序进行编译,然后将编译后的class 文件夹复制到 /Users/xuxliu/Ifoods/Java 路径下
这里还需要删除掉 .java 文件,因为父委托机制会破坏我们的测试代码
程序在执行过程中,主要步骤是
1. myClassLoader.loadClass("thread.chapt1.HelloWorld");
2. 执行 readClassBytes 读取 class 文件
3. findClass 找到class
4. 在 findClass 方法中最后执行 defineClass 就定义好了,HelloWorld 类
5. Object helloWorld = aClass.newInstance(); 创建 HelloWorld 类对象
6. Method welcome = aClass.getMethod("welcome"); 反射获取方法
7. String result = (String) welcome.invoke(helloWorld); 反射执行方法

双亲委托机制

类加载中最重要的机制,双亲委托机制又称为是父委托机制,一个类加载器调用LoadClass 之后,它并不会直接将其加载,而是先交给当前父加载器尝试加载直到最顶层的父加载器。然后再依次向下加载。

5dcda29eedc434c1898357d65ef0e2d0.png

上面的例子中,为了防止双亲委托机制干扰到我们的测试,所以在执行的时候,将 .java 文件删除了。当然也是有不用删除就能让双亲委托机制不干扰测试代码的,也就是Load class 的时候,使用自定义的类。

protected Class<?> loadClass(String name, boolean resolve)
        throws ClassNotFoundException
    {
        synchronized (getClassLoadingLock(name)) {
            // First, check if the class has already been loaded
            Class<?> c = findLoadedClass(name);
            if (c == null) {
                long t0 = System.nanoTime();
                try {
                    if (parent != null) {
                        c = parent.loadClass(name, false);
                    } else {
                        c = findBootstrapClassOrNull(name);
                    }
                } catch (ClassNotFoundException e) {
                    // ClassNotFoundException thrown if class not found
                    // from the non-null parent class loader
                }

                if (c == null) {
                    // If still not found, then invoke findClass in order
                    // to find the class.
                    long t1 = System.nanoTime();
                    c = findClass(name);

                    // this is the defining class loader; record the stats
                    sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
                    sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
                    sun.misc.PerfCounter.getFindClasses().increment();
                }
            }
            if (resolve) {
                resolveClass(c);
            }
            return c;
        }
    }

以上代码是LoadClass 中的源码,从源码中加载类的方式是

1. 当前类加载器中根据类的全路径名查询是否存在该类,如果存在则直接返回
2. 如果不存在,查看当前类是否存在父类加载器,存在,则直接调用父类加载器加载类
3. 如果当前父类不存在加载器,那么直接调用根类加载器对类进行加载
4. 如果当前类的所有父类加载器都没有成功加载class,则尝试调用当前类加载器的findClass 方法加载,这个方法就是我们定义
   加载器需要重写的方法

所以有两种方式可以绕过系统类加载器,直接讲扩展类加载器作为MyClassLoader 的父加载器,也就是上面我们另外的一个构造函数

    public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {

        ClassLoader classLoader = MyClassLoader.class.getClassLoader();

//     这个是原来的构造函数
//      MyClassLoader myClassLoader = new MyClassLoader();
        
//     这个是新的指定加载器的构造函数
       MyClassLoader myClassLoader = new MyClassLoader("/Users/xuxliu/Ifoods/Java/", classLoader);

//    这也也可以破坏,双委托机制
//     MyClassLoader myClassLoader = new MyClassLoader("/Users/xuxliu/Ifoods/Java/", null);

        Class<?> aClass = myClassLoader.loadClass("thread.chapt1.HelloWorld");

        System.out.println(aClass.getClassLoader());

        Object helloWorld = aClass.newInstance();

        System.out.println(helloWorld);

        Method welcome = aClass.getMethod("welcome");
        String result = (String) welcome.invoke(helloWorld);

        System.out.println(result);
    }

上面的加载机制都还是没有避免一层一层的委托,也可以绕过这种机制,那么就是重写loadClass 方法。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值