类加载的过程

2019-12-31  本文已影响0人  掩流年

类从编译到执行的过程

类从编译到执行的过程主要经历了以下几个步骤:
例如加载ClassLoadTest.java文件
1.编译器将ClassLoadTest.java源文件编译成ClassLoadTest.class字节码文件。
2.ClassLoader将字节码文件转换为JVM中的Class<ClassLoadTest>对象。
3.JVM利用Class<ClassLoadTest>对象实例化ClassLoadTest对象。

看一个加载的例子

Class aClass = ClassLoadTest.class; 

ClassLoader classLoader = ClassLoadTest.class.getClassLoader();
Class class3 = classLoader.loadClass("com.reflect.ClassLoadTest");

如之上代码所示,其实本质上是等价的。在java虚拟机规范中讲明了这一点。

If D was defined by a user-defined class loader, then that same user-defined class loader initiates loading of C (§5.3.2).

其中涉及到两个名词。我们称触发一个类加载的过程的类加载器叫做initiating loader,而最终加载这个类的类加载器叫做defining loader。例子:一个类加载器X触发了java.util.Map类的加载,但是它最终被系统的类加载器加载了(因为双亲委派模型),我们称X为initiating loader,系统的类加载器为defining loader。

如果使用Idea中的ASM插件去查看字节码的话,会发现Class aClass = ClassLoadTest.class;这段代码执行了LDC指令,会去常量池中拿东西。我们假设这段代码是运行在Test类中的,这时候,ClassLoadTest.class的加载会被Test.class.classLoader作为initiating loader触发。
ClassLoader classLoader = A.class.getClassLoader();其中的classLoader就是最终加载A.class的类加载器。
我们令classLoader.loadClass("com.ClassLoadTest");就是令该类加载器加载ClassLoadTest.class

什么是ClassLoader

ClassLoader是Java中的核心组件,它主要是从系统外部读取class文件字节码,加载进JVM中,交由JVM进行连接初始化等。

ClassLoader的种类

    protected Class<? > findClass(String name) throws ClassNotFoundException {
        throw new ClassNotFoundException(name);
    }

它的定义直接抛出异常,它的作用是提供给用户覆盖方法,编写自定义的ClassLoader。自定义编写ClassLoader的代码如下:

public class MyClassLoader extends ClassLoader {

    private String fileName;
    private String path;

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

    @Override
    public Class findClass(String name) {
        byte[] b = new byte[0];
        try {
            b = loadClassData(name, path);
        } catch (IOException e) {
            e.printStackTrace();
        }
        return defineClass(name, b, 0, b.length);
    }

    private byte[] loadClassData(String name, String path) throws IOException {
        return Files.readAllBytes(Paths.get(path + name + ".class"));

    }

}

这样就完成了一个类加载器的编写。我们用写一个检查类进行验证

class Check{
    public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException,
            InstantiationException {
        MyClassLoader myClassLoader = new MyClassLoader("LoadFile","\\mypath");
        Class c = myClassLoader.loadClass("LoadFile");
        System.out.println(c.getClassLoader());
        c.newInstance();
    }
}

打印的结果为

com.reflect.MyClassLoader@7d4991ad
load file ...

类加载器的双亲委派机制

双亲委派机制

如上图所示,在开始类加载之前,类加载器首先会检查这个类曾经有没有被加载过。加载过则直接使用加载的Class对象,没有加载则向上委托给它的父加载器,检查父加载器有没有加载过。
如果都没有加载过这个类,则会从上到下检查classpath中的jar,查找包中的jar是否有对应的类。
使用双亲委派机制来加载类,主要是避免多份同样字节码的加载。

loadClass和forName的区别

类的装载过程主要有以下三步:

对于Class.forName和ClassLoad.loadClass而言,主要的区别是:
Class.forName得到的class是完成初始化的。 ClassLoad.loadClass得到的class是没有链接的,也就是没有初始化的。
这点可以在源代码中体现:
forName的代码,可以看到它的返回方法中第二个参数置为true,则表明会被初始化。

    @CallerSensitive
    public static Class<?> forName(String className)
                throws ClassNotFoundException {
        Class<?> caller = Reflection.getCallerClass();
        return forName0(className, true, ClassLoader.getClassLoader(caller), caller);
    }

对于loadClass而言,它最后执行的方法如下,它的初始化默认是为false的。

    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;
        }
    }

由于它的方法是protected修饰的,所以子类无法继承。但是我们有反射呀...在Java8中,有了反射就可以随时随地的扒下JVM的内裤了。。。

上一篇 下一篇

猜你喜欢

热点阅读