Java类加载器

25 阅读3分钟

类加载器

我们写的每一个Java文件,首先通过编译器编译成class文件,然后经过类加载器加载到jdk运行时内存中生成一个class类,才会被程序使用。而类加载器就是加载字节码(.class)文件的类--java.lang.ClassLoader

类加载器的分类

Java默认设置了三个类加载器。

  • BootstrapClassloader
  • ExtClassloader
  • AppClassloader

BootstrapClassloader 叫做启用类加载器,用于加载JRE核心类库,使用C++实现。加载路径%JAVA_HOME%/lib下的所有类库。

ExtClassloader 扩展类加载器,加载%JAVA_HOME%/lib/ext中的所有类库。

AppClassloader 应用类加载器也叫系统类加载器System Classloader,加载%CLASSPATH%路径下的所有类库。

Java 也提供了扩展,可以让我们自己实现类加载的功能。类加载器在Java中是java.lang.ClassLoader这个类,如果要自定义类加载器,只要实现这个类,重写加载方法就好了。

在Java中,由不同的类加载器加载的两个相同的类在Java虚拟机里是两个不同的类,那么Java是怎么确保一个类不会被多个类加载器重复加载,并且保证核心API不会被篡改的呢?

这就需要Java的双亲委派机制。

双亲委派机制

双亲委派机制

1、当一个类加载器接到加载类的请求后,首先会交给父类去加载,如果所有父类都无法加载,自己加载,并将被加载的类缓存起来。。

2、每加载一个类,所有的类加载器都会判断是否可以加载,最终会委托到启动类加载器来首先加载。所有的类的加载都尽可能由顶层的类加载器加载,这样就保证了加载的类的唯一性。

3、启动类加载器、扩展类加载器、应用程序类加载器,都有自己加载的类的范围,因此并不是所有的类父类都可以加载。

Java 类加载器中还有一个全盘委托机制,当指定一个ClassLoader加载一个类时,该类所依赖或者引用的类也会由这个类加载器来加载,除非显示的用别的类加载器加载

比如:程序入口默认用的是AppClassloader,那么以后创建出来的类也是用AppClassloader来加载,除非自己显示的用别的类加载器去加载。(classX引用了classY,那么ClassX的类加载器就会去加载classY(前提是classY尚未被加载))

线程上下文加载器

因为双亲委派机制的存在,每个类加载器都有自己的加载范围。但是在JDK中提供了很多服务提供者接口(Service Provider Interface,SPI),他们允许第三方来实现接口,比如常见的JDBC、JNDI、JAXP等。

这些接口是由Java核心库提供的,并由启用类加载器(BootstrapClassloader)加载,而实现确是在Java应用所依赖的第三方Jar包里的,默认是由应用类加载器(AppClassloader)加载。

Java引入了线程上下文加载器的概念来打破双亲委派机制。 通过线程Thread类的getContextClassLoader()setContextClassLoader(ClassLoader cl)来设置线程上下文加载器。如果没有通过setContextClassLoader(ClassLoader cl)进行设置的话,线程将继承其父线程的上下文类加载器。

线程上下文加载器并不是一个真实存在的类,而是一个概念。它是Thread类里的一个成员变量。

public class Thread implements Runnable {

   private ClassLoader contextClassLoader;

   public void setContextClassLoader(ClassLoader cl) {
       SecurityManager sm = System.getSecurityManager();
       if (sm != null) {
           sm.checkPermission(new RuntimePermission("setContextClassLoader"));
       }
       contextClassLoader = cl;
   }



   public ClassLoader getContextClassLoader() {
       if (contextClassLoader == null)
           return null;
       SecurityManager sm = System.getSecurityManager();
       if (sm != null) {
           ClassLoader.checkClassLoaderPermission(contextClassLoader,
                                                  Reflection.getCallerClass());
       }
       return contextClassLoader;
   }
}