一篇文章搞定JVM如何加载.class文件

一、Java虚拟机

JVM是一个内存中的虚拟机,主要运用内存存储,所有类、类型、方法,都是在内存中,这决定着我们的程序运行是否健壮、高效。

二、.class文件加载执行流程图

在这里插入图片描述

  • Class Loader(类加载器):依据特定格式,加载class文件到内存
  • Execution Engine:(执行引擎)对命令进行解析,解析完毕之后就可以提交到操作系统里面执行了
  • Native Interface:融合不同的开发语言的原生库为Java所用,性能并不如c/c++高,主流的JVM也是由c/c++实现的
  • Runtime Data Area:JVM内存空间结构模型,所写的程序都会被加载到这里

三、一个类从编译到加载再到执行的流程

这里我们用一个例子来进行解释

package com.mtli.reflect;

/**
 * @Description:
 * @Author: Mt.Li
 * @Create: 2020-04-25 13:55
 */
public class Robot {
    private String name;
    public void sayHi(String helloSentence){
        System.out.println(helloSentence + " " + name);
    }

    private String throwHello(String tag){
        return "Hello " + tag;
    }
}
  • 编译器将Robot.java源文件编译为Robot.class字节码文件
  • ClassLoader将字节码转换为JVM中的Class对象
  • JVM利用Class对象实例化为Robot对象

3.1、ClassLoader(类加载器)

  • ClassLoader 在Java中有着非常重要的作用,主要工作在Class装载的加载阶段,其主要作用是从系统外部获得Class二进制数据流。它是Java的核心组件,所有的Class都是由ClassLoader进行加载的,ClassLoader负责通过将Class文件里的二进制数据流加载进系统,然后交给Java虚拟机进行连接、初始化等操作。

  • 它的种类如下
    在这里插入图片描述

  • 还有自定义ClassLoader:Java编写,定制化加载(加载的可以不是class,java,可以是其他类型的)

  • 想要实现自定义ClassLoader,我们需要重写两个关键函数

    • findClass(根据名称或者位置加载.class字节码,然后使用defindClass)和defineClass(生成.class)
  • 可能有人说咋还要生成.class啊,因为findClass引入进来的可能是java语言编译成的.class也有可能是其他语言编译成的,甚至可能是自定义的,我们需要用defineClass将字节数组流解密之后,将该字节流数组生成JVM能够识别的字节码文件。

3.2、自定义CLassLoader

package com.mtli.reflect;

import java.io.*;

/**
 * @Description:自定义ClassLoader
 * @Author: Mt.Li
 * @Create: 2020-04-25 15:37
 */


public class MyClassLoader extends ClassLoader{
    private String path;
    private String classLoaderName;

    public MyClassLoader(String path, String classLoaderName){
        this.path = path;
        this.classLoaderName = classLoaderName;
    }

    // 重写findClass用于寻找类文件
    // defineClass还是用Java自己的
    @Override
    public Class findClass(String name){
        // 以二进制字节流传递进来,然后交给defind
        byte[] b = loadClassData(name);
        return defineClass(name, b, 0, b.length);
    }

    // 用于加载类文件
    private byte[] loadClassData(String name) {
        name = path + "/" + name + ".class";
        InputStream in = null;
        ByteArrayOutputStream out = null;
        try{
            in = new FileInputStream(new File(name));
            // 通过ByteArrayOutputStream解密
            out = new ByteArrayOutputStream();
            int i = 0;
            // 没读完继续读
            while((i = in.read()) != -1){
                out.write(i);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }finally {
            try {
                out.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
            try {
                in.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        // 返回字节码
        return out.toByteArray();
    }
}

我们自定义一个Java类

public class Wali{
	static{
		System.out.println("Hello Wali");
	}
}

用javac对其进行编译生成Wali.class文件

写一个测试类:

package com.mtli.reflect;

/**
 * @Description:
 * @Author: Mt.Li
 * @Create: 2020-04-25 17:08
 */
public class ClassLoaderChecker {
    public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException {
        MyClassLoader m = new MyClassLoader("C:/Users/Taogege/Desktop", "myClassLoader");
        Class c = m.loadClass("Wali");
        // 创建实例
        c.newInstance();
    }
}

执行结果:
Hello Wali

3.3、类加载器的双亲委派机制

上面我们在自定义ClassLoader中重写了它的findClass方法,对于实际应用中,我们也有必要了解findClass和loadClass的执行。

jdk1.8中的loadClass()源码:

 protected Class<?> loadClass(String name, boolean resolve)
        throws ClassNotFoundException
    {
        synchronized (getClassLoadingLock(name)) {
            // 首先我们检查要加载的类是否加载过
            Class<?> c = findLoadedClass(name);
            // c为null,则递归该方法,到当前加载器的父级继续查询
            if (c == null) {
                // native方法
                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
                }
                // 递归回来后,到这里说明根加载器也没有加载过,则开始检测能否
                // 自己寻找并加载class
                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;
        }
    }
先要说的

上面我们说了ClassLoader有多种,不同的ClassLoader加载类的方式和路径有所不同,为了实现分工,各自负责各自的区块,使得逻辑更加明确,加载类的时候各司其职,当然存在一个机制,让他们相互协作,形成一个整体,这个机制就是双亲委派机制
在这里插入图片描述
由上图,我们看到左边是由下而上检测是否曾经加载过这个类,比如先检测用户自定义类加载器
是否加载过这个类,有则提交给JVM使用,节省时间,节省内存空间,没有则继续向它的父级查询,重复步骤,如果没有继续向上直到根加载器,如果还是没有,则会像如图右边,当前的类加载器会自己去加载当前的类,如果不能加载,则向下,看子加载器能否加载,直到最下边,如果还是不能加载,则抛出ClassNotFoundException 异常。这跟我们上边放出的源码流程是差不多的。

为什么要用双亲委派机制去加载类呢

内存空间是宝贵的,我们没有必要保存多份同样的类,所以每次加载类进行查询是否曾经加载过,也就是避免多个同样字节码的加载。

四、类的加载方式

  • 隐式加载:new
    • 这个 方式大家都知道,直接创建类
  • 显式加载:loadClass,forName

4.1、loadClass和forName的区别

  • Class.forName也是可以对类进行加载的,内部实际调用的方法是 Class.forName(className,true,classloader);
    注意它的第二个参数为true,这个参数表示是否进行初始化,默认为true,它会让jvm对指定的类执行加载、连接、初始化操作。
  • ClassLoader.loadClass:从上边的加载分析可以知道,它只负责连接,不会进行初始化等操作,所以static这样的静态代码是不会进行初始化的

文章的最后附上一张类的装载过程便于自己查看:
在这里插入图片描述

原创文章 50 获赞 101 访问量 1万+

猜你喜欢

转载自blog.csdn.net/weixin_42173451/article/details/105793923