IT序号网

java中类加载器入门

leader 2022年04月08日 编程语言 197 0

前言

顾名思义,类加载器就是负责类的加载的,从内存中,class文件中,jar包中等渠道加载,对于任意一个class,都需要由加载它的类加载器和这个类本身确定其在JVM中的唯一性。

java内置类加载器

java中内置了3种类加载器

根类加载器

又叫做Bootstrap类加载器,是最顶层的类加载器,没有父类加载器,是C++语言编写的,主要负责java中核心类库的加载,如java.lang包下所有类。

public class Test { 
 
  public static void main(String[] args) { 
    System.out.println(String.class.getClassLoader()); 
    String property = System.getProperty("sun.boot.class.path"); 
    for (String path : property.split(";")) { 
      System.out.println(path); 
    } 
  } 
} 

输出为

null 
D:\java\jdk\jdk1.8.0_181\jre\lib\resources.jar 
D:\java\jdk\jdk1.8.0_181\jre\lib\rt.jar 
D:\java\jdk\jdk1.8.0_181\jre\lib\sunrsasign.jar 
D:\java\jdk\jdk1.8.0_181\jre\lib\jsse.jar 
D:\java\jdk\jdk1.8.0_181\jre\lib\jce.jar 
D:\java\jdk\jdk1.8.0_181\jre\lib\charsets.jar 
D:\java\jdk\jdk1.8.0_181\jre\lib\jfr.jar 
D:\java\jdk\jdk1.8.0_181\jre\classes 

根类加载器获取不到引用,所以为null,常用的类如String都是在rt.jar这个jar中的。

扩展类加载器

由java语言实现,父类加载器为根类加载器,主要加载JAVA_HOME下jre\lib\ext下的目录

import jdk.nashorn.tools.Shell; 
 
public class Test { 
 
  public static void main(String[] args) { 
    System.out.println(Shell.class.getClassLoader()); 
    String property = System.getProperty("java.ext.dirs"); 
    for (String path : property.split(";")) { 
      System.out.println(path); 
    } 
  } 
} 

输出为

sun.misc.Launcher$ExtClassLoader@6d6f6e28 
D:\java\jdk\jdk1.8.0_181\jre\lib\ext 
C:\WINDOWS\Sun\Java\lib\ext 

Shell类就是nashorn.jar中的一个类,类加载器确实为扩展类加载器。

static class ExtClassLoader extends URLClassLoader { 
    private static volatile Launcher.ExtClassLoader instance; 
 
    public static Launcher.ExtClassLoader getExtClassLoader() throws IOException { 
      if (instance == null) { 
        Class var0 = Launcher.ExtClassLoader.class; 
        synchronized(Launcher.ExtClassLoader.class) { 
          if (instance == null) { 
            instance = createExtClassLoader(); 
          } 
        } 
      } 
 
      return instance; 
    } 
private static Launcher.ExtClassLoader createExtClassLoader() throws IOException { 
      try { 
        return (Launcher.ExtClassLoader)AccessController.doPrivileged(new PrivilegedExceptionAction<Launcher.ExtClassLoader>() { 
          public Launcher.ExtClassLoader run() throws IOException { 
            File[] var1 = Launcher.ExtClassLoader.getExtDirs(); 
            int var2 = var1.length; 
 
            for(int var3 = 0; var3 < var2; ++var3) { 
              MetaIndex.registerDirectory(var1[var3]); 
            } 
 
            return new Launcher.ExtClassLoader(var1); 
          } 
        }); 
      } catch (PrivilegedActionException var1) { 
        throw (IOException)var1.getException(); 
      } 
    } 
private static File[] getExtDirs() { 
      String var0 = System.getProperty("java.ext.dirs"); 
      File[] var1; 
      if (var0 != null) { 
        StringTokenizer var2 = new StringTokenizer(var0, File.pathSeparator); 
        int var3 = var2.countTokens(); 
        var1 = new File[var3]; 
 
        for(int var4 = 0; var4 < var3; ++var4) { 
          var1[var4] = new File(var2.nextToken()); 
        } 
      } else { 
        var1 = new File[0]; 
      } 
 
      return var1; 
    } 
} 

扩展类加载器的定义如上,使用了双重锁判断的单例写法。

系统类加载器

也是java语言实现的,父类加载器为扩展类加载器,加载classpath下的类,如我们引入的第三方jar包和我们自己定义的类。

public class Test { 
 
  public static void main(String[] args) { 
    System.out.println(Test.class.getClassLoader()); 
    String property = System.getProperty("java.class.path"); 
    for (String path : property.split(";")) { 
      System.out.println(path); 
    } 
  } 
} 

输出为

sun.misc.Launcher$AppClassLoader@18b4aac2 
D:\java\jdk\jdk1.8.0_181\jre\lib\charsets.jar 
D:\java\jdk\jdk1.8.0_181\jre\lib\deploy.jar 
D:\java\jdk\jdk1.8.0_181\jre\lib\ext\access-bridge-64.jar 
D:\java\jdk\jdk1.8.0_181\jre\lib\ext\cldrdata.jar 
D:\java\jdk\jdk1.8.0_181\jre\lib\ext\dnsns.jar 
D:\java\jdk\jdk1.8.0_181\jre\lib\ext\jaccess.jar 
D:\java\jdk\jdk1.8.0_181\jre\lib\ext\jfxrt.jar 
D:\java\jdk\jdk1.8.0_181\jre\lib\ext\localedata.jar 
D:\java\jdk\jdk1.8.0_181\jre\lib\ext\nashorn.jar 
D:\java\jdk\jdk1.8.0_181\jre\lib\ext\sunec.jar 
D:\java\jdk\jdk1.8.0_181\jre\lib\ext\sunjce_provider.jar 
D:\java\jdk\jdk1.8.0_181\jre\lib\ext\sunmscapi.jar 
D:\java\jdk\jdk1.8.0_181\jre\lib\ext\sunpkcs11.jar 
D:\java\jdk\jdk1.8.0_181\jre\lib\ext\zipfs.jar 
D:\java\jdk\jdk1.8.0_181\jre\lib\javaws.jar 
D:\java\jdk\jdk1.8.0_181\jre\lib\jce.jar 
D:\java\jdk\jdk1.8.0_181\jre\lib\jfr.jar 
D:\java\jdk\jdk1.8.0_181\jre\lib\jfxswt.jar 
D:\java\jdk\jdk1.8.0_181\jre\lib\jsse.jar 
D:\java\jdk\jdk1.8.0_181\jre\lib\management-agent.jar 
D:\java\jdk\jdk1.8.0_181\jre\lib\plugin.jar 
D:\java\jdk\jdk1.8.0_181\jre\lib\resources.jar 
D:\java\jdk\jdk1.8.0_181\jre\lib\rt.jar 
C:\Program Files\JetBrains\IntelliJ IDEA 2019.1.3\lib\idea_rt.jar 

系统类加载器的定义

public Launcher() { 
    Launcher.ExtClassLoader var1; 
    try { 
      var1 = Launcher.ExtClassLoader.getExtClassLoader(); 
    } catch (IOException var10) { 
      throw new InternalError("Could not create extension class loader", var10); 
    } 
 
    try { 
      this.loader = Launcher.AppClassLoader.getAppClassLoader(var1); 
    } catch (IOException var9) { 
      throw new InternalError("Could not create application class loader", var9); 
    } 
 
    Thread.currentThread().setContextClassLoader(this.loader); 
    String var2 = System.getProperty("java.security.manager"); 
    if (var2 != null) { 
      SecurityManager var3 = null; 
      if (!"".equals(var2) && !"default".equals(var2)) { 
        try { 
          var3 = (SecurityManager)this.loader.loadClass(var2).newInstance(); 
        } catch (IllegalAccessException var5) { 
        } catch (InstantiationException var6) { 
        } catch (ClassNotFoundException var7) { 
        } catch (ClassCastException var8) { 
        } 
      } else { 
        var3 = new SecurityManager(); 
      } 
 
      if (var3 == null) { 
        throw new InternalError("Could not create SecurityManager: " + var2); 
      } 
 
      System.setSecurityManager(var3); 
    } 
 
  } 

先创建扩展类加载器,将扩展类加载器作为父类加载器创建系统类加载器,并设置到当前线程的上下文类加载器中。

static class AppClassLoader extends URLClassLoader { 
    final URLClassPath ucp = SharedSecrets.getJavaNetAccess().getURLClassPath(this); 
 
    public static ClassLoader getAppClassLoader(final ClassLoader var0) throws IOException { 
      final String var1 = System.getProperty("java.class.path"); 
      final File[] var2 = var1 == null ? new File[0] : Launcher.getClassPath(var1); 
      return (ClassLoader)AccessController.doPrivileged(new PrivilegedAction<Launcher.AppClassLoader>() { 
        public Launcher.AppClassLoader run() { 
          URL[] var1x = var1 == null ? new URL[0] : Launcher.pathToURLs(var2); 
          return new Launcher.AppClassLoader(var1x, var0); 
        } 
      }); 
    } 
 
    AppClassLoader(URL[] var1, ClassLoader var2) { 
      super(var1, var2, Launcher.factory); 
      this.ucp.initLookupCache(this); 
    } 

类加载流程

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; 
        } 
    } 
  1. 首先检查该类是否已经加载过,如果是,直接返回已经加载过的类
  2. 父类加载器存在,委托父类加载器加载,不存在,委托根类加载器
  3. 都不能加载的话,由当前类加载器加载,重写findClass()方法
protected Class<?> findClass(final String name) 
        throws ClassNotFoundException 
    { 
        final Class<?> result; 
        try { 
            result = AccessController.doPrivileged( 
                new PrivilegedExceptionAction<Class<?>>() { 
                    public Class<?> run() throws ClassNotFoundException { 
                        String path = name.replace('.', '/').concat(".class"); 
                        Resource res = ucp.getResource(path, false); 
                        if (res != null) { 
                            try { 
                                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) { 
            throw new ClassNotFoundException(name); 
        } 
        return result; 
    } 

URLClassLoader就是通过重写findClass()实现的。

打破双亲委派机制

双亲委派机制保证了越基础的类由越上层的类加载器加载,基础类之所以被称为“基础”,是因为它们总是作为被调用的API。但是,如果基础类要调用用户的代码,那该怎么办呢。一个典型的例子就是JDBC

jdk提供了所有相关的接口,这些接口都是由根类加载器加载的,而第三方驱动的实现是由系统类加载器加载的,按照双亲委派机制,根类加载器不可能加载得到第三方驱动的实现,为了解决这个困境,java引入了一种不太优雅的设计,线程上下文类加载器,这就变成了父类加载器委派子类加载器去加载,打破了双亲委派机制。java中所有涉及到SPI的动作基本上都是采用的这种方式。



参考

类加载器如何打破双亲委派加载机制(SPI原理)


评论关闭
IT序号网

微信公众号号:IT虾米 (左侧二维码扫一扫)欢迎添加!

java中正则表达式的group用法