Java-类加载机制

版权声明:欢迎转载,请注明作者和出处 https://blog.csdn.net/baichoufei90/article/details/85931559

Java-类加载机制

摘要

本文简要介绍Java加载机制,还会介绍双亲委派机制的破坏,线程上下文加载器,以及JDBC Driver是如何自动加载的。

未完成

0x01 Java类加载机制

1.1 简介

当前版本jdk是采用双亲委派机制:

类加载机制

子ClassLoader总是会让父ClassLoader尝试加载,如果不行,才会自己尝试加载。

1.1.1 BootstrapClassLoader

BootstrapClassLoader是启动类加载器。

通过以下代码打出BootstrapClassLoader加载的文件:

public class BootStrapTest
{
    public static void main(String[] args)
    {
      URL[] urls = sun.misc.Launcher.getBootstrapClassPath().getURLs();
      for (int i = 0; i < urls.length; i++) {
          System.out.println(urls[i].toExternalForm());
       }
    }
}

结果如下:

file:/Library/Java/JavaVirtualMachines/jdk1.8.0_131.jdk/Contents/Home/jre/lib/resources.jar
file:/Library/Java/JavaVirtualMachines/jdk1.8.0_131.jdk/Contents/Home/jre/lib/rt.jar
file:/Library/Java/JavaVirtualMachines/jdk1.8.0_131.jdk/Contents/Home/jre/lib/sunrsasign.jar
file:/Library/Java/JavaVirtualMachines/jdk1.8.0_131.jdk/Contents/Home/jre/lib/jsse.jar
file:/Library/Java/JavaVirtualMachines/jdk1.8.0_131.jdk/Contents/Home/jre/lib/jce.jar
file:/Library/Java/JavaVirtualMachines/jdk1.8.0_131.jdk/Contents/Home/jre/lib/charsets.jar
file:/Library/Java/JavaVirtualMachines/jdk1.8.0_131.jdk/Contents/Home/jre/lib/jfr.jar
file:/Library/Java/JavaVirtualMachines/jdk1.8.0_131.jdk/Contents/Home/jre/classes

可以看到除了rt.jar以外还加载了一些其他文件。

1.2 双亲委派的意义

比如java.lang.Object,用户也自定义一个同样权限定名的类。但在加载时,会首先用BootstrapClassLoader加载rt.jar中的该类。而用户自定义的Object类,也会因为AppClassLoader往上寻找到祖先BootstrapClassLoader类来加载该类,但会发现该类已经被加载过导致报错。

0x02 双亲委派机制的破坏

2.1 破坏1

JDK1.2之前没有双亲委派模型,所以之前的开发者继承java.lang.ClassLoader后要做的就是重写loadClass()方法,编写自定义的类加载逻辑。该loadClass方法代码如下:

// 父加载器
private final ClassLoader parent;
protected Class<?> loadClass(String name, boolean resolve)
        throws ClassNotFoundException
    {
        // 先在ClassLoader获取该ClassName对应的同步锁对象
        synchronized (getClassLoadingLock(name)) {
            // First, check if the class has already been loaded
            // 先检查该类全限定名是否已经被加载到JVM
            Class<?> c = findLoadedClass(name);
            if (c == null) {
            // 此时没有加载该类
                long t0 = System.nanoTime();
                try {
                    if (parent != null) {
                    // 存在父加载器(不是BootStrapClassLoader)
                        // 就尝试让父加载器加载类,但不连接
                        c = parent.loadClass(name, false);
                    } else {
                        // 否则尝试用BootStrapClassLoader加载该类
                        c = findBootstrapClassOrNull(name);
                    }
                } catch (ClassNotFoundException e) {
                    // ClassNotFoundException thrown if class not found
                    // from the non-null parent class loader
                }

                if (c == null) {
                // 此时还没有加载该类
                    long t1 = System.nanoTime();
                    // 就调用findClass方法来查找该类
                    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;
        }
    }

但在JDK1.2之后,不再提倡重写loadClass(),而是应该重写新加入的findClass方法:

protected Class<?> findClass(String name) throws ClassNotFoundException {
	// 该方法默认没有实现,只是抛出一个ClassNotFoundException
    throw new ClassNotFoundException(name);
}

启动类加载器BootstrapClassLoader是用C++实现,而扩展类加载器ExtClassLoader和应用程序类加载器AppClassLoader都继承自URLClassLoaderfindClass方法直接用的URLClassLoaderfindClass方法:

// 要加载的类的全限定名
// 比如是demos.classInitialization.classloader.order.EntityC
protected Class<?> findClass(final String name)
        throws ClassNotFoundException
{
    final Class<?> result;
    try {
        result = AccessController.doPrivileged(
            new PrivilegedExceptionAction<Class<?>>() {
                public Class<?> run() throws ClassNotFoundException {
                    // 那么这里path为demos/classInitialization/classloader/order/EntityC.class
                    String path = name.replace('.', '/').concat(".class");
                    Resource res = ucp.getResource(path, false);
                    if (res != null) {
                        try {
                            // 使用从指定Resource获取的类字节来转为Class对象 
                            // 必须先连接,然后生成的类才能使用它。
                            return defineClass(name, res);
                        } catch (IOException e) {
                            throw new ClassNotFoundException(name, e);
                        }
                    } else {
                        return null;
                    }
                }
            }, acc);
    } catch (java.security.PrivilegedActionException pae) {
        throw (ClassNotFoundException) pae.getException();
    }
    if (result == null) {
        // 如果结果为空,直接抛ClassNotFoundException
        throw new ClassNotFoundException(name);
    }
    // 返回得到的Class对象
    return result;
}

接着看看URLClassLoaderdefineClass方法:

/*
 * 使用从指定的资源中获取的class bytes 来定义一个Class对象
 * 最终得到的Class必须在使用前被解析
 */
private Class<?> defineClass(String name, Resource res) throws IOException {
    long t0 = System.nanoTime();
    int i = name.lastIndexOf('.');
    // file:/xxx/javaDemos/target/classes/
    URL url = res.getCodeSourceURL();
    if (i != -1) {
        String pkgname = name.substring(0, i);
        // 检查包是否已经加载过
        Manifest man = res.getManifest();
        // 
        definePackageInternal(pkgname, man, url);
    }
    // 从class字节码中读取数据并转为Class
    java.nio.ByteBuffer bb = res.getByteBuffer();
    if (bb != null) {
        // 直接使用ByteBuffer读取(字节缓冲)
        CodeSigner[] signers = res.getCodeSigners();
        CodeSource cs = new CodeSource(url, signers);
        sun.misc.PerfCounter.getReadClassBytesTime().addElapsedTimeFrom(t0);
        return defineClass(name, bb, cs);
    } else {
        byte[] b = res.getBytes();
        // must read certificates AFTER reading bytes.
        CodeSigner[] signers = res.getCodeSigners();
        CodeSource cs = new CodeSource(url, signers);
        sun.misc.PerfCounter.getReadClassBytesTime().addElapsedTimeFrom(t0);
        return defineClass(name, b, 0, b.length, cs);
    }
}

2.2 破坏2-线程上下文加载器

用父加载器加载的无法直接去加载子加载器的内容。

2.3 破坏3-OSGI

猜你喜欢

转载自blog.csdn.net/baichoufei90/article/details/85931559