23 September 2018

类加载器

1.背景:jdk,jre,jvm

我们利用jdk来开发属于我们自己的java程序,通过jdk的编译器(javac)将我们的java文件编译成java字节码,在jre上运行这些java字节码,使用jre内嵌的jvm解析这些字节码,映射到cpu指令集或操作系统的调用

2.类加载器介绍

class loaders属于jre的一部分,负责在运行时将java类动态加载到jvm,得益于class loaders,jvm在无需知晓底层文件或文件系统时就可以运行java程序

此外java类是按需加载,并不会一次全部加载到内存中

java类由java.lang.ClassLoader的实例进行加载,但是class loader本身也是java类,它又由谁加载呢?这就是bootstrap class loader的用处了,它主要负责加载jdk的核心类库

bootstrap class loader:负责加载jdk核心类库,$JAVA_HOME/jre/lib下的核心库 + rt.jarnative代码编写,不会以java类的形式体现,在不同jvm中行为会有所不同。

extension class loader:加载核心库之外的扩展类,JAVA_HOME/lib/ext,正因如此所有的应用程序都能运行在java平台上。

app class loader:负责加载所有应用程序级别的类(自己代码写的类),classpath环境变量或 -classpath以及-cp命令行参数中指定的文件

3.ClassLoaders是如何工作的

当jvm请求一个类时,class loaders会通过java.lang.ClassLoader.loadClass()方法负责通过类的全限定名将class definition加载到runtime,如果类尚未加载,class loader会将加载请求委派给父类加载器,如果父类加载器最终没有找到该类,子加载器将调用 java.net.URLClassLoader.findClass() 方法从文件系统中加载该类(递归)。如果最终子加载器也无法加载该类,将抛出 java.lang.NoClassDefFoundErrorjava.lang.ClassNotFoundException

java.lang.ClassNotFoundException: com.baeldung.classloader.SampleClassLoader 
    at java.net.URLClassLoader.findClass(URLClassLoader.java:381)    
    at java.lang.ClassLoader.loadClass(ClassLoader.java:424)    
    at java.lang.ClassLoader.loadClass(ClassLoader.java:357)    
    at java.lang.Class.forName0(Native Method)    
    at java.lang.Class.forName(Class.java:348)

4.自定义ClassLoader

自定义classLoader不仅仅只用于在运行时加载类,还有这么一些场景

1.用于更新已存在的字节码

2.根据需求动态创建类

3.在加载具有相应类名,包名的类的字节码时可以实现类的版本控制机制

public class CustomClassLoader extends ClassLoader {
    public CustomClassLoader(ClassLoader parent) {
        super(parent);
    }
    public Class getClass(String name) throws ClassNotFoundException {
        byte[] b = loadClassFromFTP(name);
        return defineClass(name, b, 0, b.length);
    }
    @Override
    public Class loadClass(String name) throws ClassNotFoundException {
        if (name.startsWith("com.baeldung")) {
            System.out.println("Loading Class from Custom Class Loader");
            return getClass(name);
        }
        return super.loadClass(name);
    }
    private byte[] loadClassFromFTP(String fileName)  {
        // Returns a byte array from specified file.
    }
}

自定义的classLoader用于从包com.baeldung加载文件,扩展了class loader

5.理解java.lang.ClassLoader

loadClass方法:通过完全限定类名加载类

resolve == true,jvm将执行loadClass()解析该类,然而我们一般只想判断类是否存在

1.执行findLoadedClass(String)判断类是否已经加载了

2.执行父类的loadClass(String)方法

3.执行findClass(String)方法查找类

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.
                    c = findClass(name);
                }
            }
            if (resolve) {
                resolveClass(c);
            }
            return c;
        }
}

defineClass()方法

负责将字节数组转换为类,我们需要在使用类之前先解析类,final修饰

protected final Class<?> defineClass(
  String name, byte[] b, int off, int len) throws ClassFormatError

getParent()方法

返回父加载器用于委派,null代表bootstrap class loader

@CallerSensitive
    public final ClassLoader getParent() {
        if (parent == null)
            return null;
        SecurityManager sm = System.getSecurityManager();
        if (sm != null) {
            // Check access to the parent class loader
            // If the caller's class loader is same as this class loader,
            // permission check is performed.
            checkClassLoaderPermission(parent, Reflection.getCallerClass());
        }
        return parent;
    }

getResource()方法

用于查找给定名称的资源

首先,查找请求会委托给父加载器,如果父加载器为null,则将请求交给bootstrap class loader,如果依然失败,该方法将调用findResource(String)来查找资源,返回一个用于读取资源的URL对象,如果没有找到资源或者没有足够的权限方法资源返回null,java会从classpath路径中加载资源

public URL getResource(String name) {
        URL url;
        if (parent != null) {
            url = parent.getResource(name);
        } else {
            url = getBootstrapResource(name);
        }
        if (url == null) {
            url = findResource(name);
        }
        return url;
    }

6.线程上下文加载器(Context ClassLoaders)

场景:当jvm核心类需要加载由开发人员提供的类或者资源时,正常双亲委派模式加载出现了问题

例子:在JNDI中,其核心功能由rt.jar中的引导类(bootstrap)实现,但是这些JNDI引导类可能需要加载由各独立服务商提供的JNDI实现类(部署在应用classpath中),这个场景需要bootstrap加载一些仅对child class loader可见的类

方案:java.lang.Thread 类有一个 getContextClassLoader 方法用于返回特定线程的ContextClassLoader。在加载资源和类时,ContextClassLoader由线程的创建者提供。



blog comments powered by Disqus