1.什么是类加载器
在编写Java程序时须要应用javac命令将.java后缀名的文件编译成.class文件,而后JVM通过执行.class文件来运行咱们写的程序,那么JVM怎么能力执行.class文件呢?
这就须要类加载器了。
2.有哪几种类加载器
- BootstrapClassLoader:这个类加载器能够被称为疏导类加载器,它由C++语言编写,在JDK中看到的是应用native标注的办法。它负责加载jre/lib下的外围包,例如:rt.jar。
- ExtClassLoader:它被称为扩大类加载器,它是由Java语言编写,用于加载jre/lib/ext目录中的包。
- AppClassLoader:这个类加载器叫做应用程序类加载器,它是咱们平时应用最频繁的一品种加载器,用于加载classpath下的.class文件,也就是咱们本人写的程序。
- 自定义的类加载器:该类加载器用于加载咱们指定的一些类。
3.ClassLoader之间的关系
3.1 类加载器创立过程
在jdk中找到sun.misc.Launcher文件,这个文件是JVM中的启动器实例,在该类外部有getLauncher()
办法
public static Launcher getLauncher() { return launcher;}
它返回的是launcher
实例
private static Launcher launcher = new Launcher();
在构造方法调用时会初始化 ExtClassLoader和AppClassLoader(BootstrapClassLoader会在Java虚拟机创立之后进行加载,所以是在此之前)。
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); // 非核心的就去掉了}
通过点击 getXxxClassLoader()
跟到最初能够看到
private ClassLoader(Void unused, ClassLoader parent) { // 通过这行能够让加载器之间建设父子级关系 this.parent = parent; if (ParallelLoaders.isRegistered(this.getClass())) { parallelLockMap = new ConcurrentHashMap<>(); package2certs = new ConcurrentHashMap<>(); assertionLock = new Object(); } else { // no finer-grained lock; lock on the classloader instance parallelLockMap = null; package2certs = new Hashtable<>(); assertionLock = this; }}
留神: 咱们查看ExtClassLoader时会发现这里的parent是null,而查看AppClassLoader时传入的parent是ExtClassLoader的对象实例。所以这里也就印证了下面画的图。
3.2 探索加载类的流程
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException{ synchronized (getClassLoadingLock(name)) { // 查看指标类是否已加载,如果已加载则返回 Class<?> c = findLoadedClass(name); // 没有加载到 if (c == null) { long t0 = System.nanoTime(); try { // 以后加载器的父加载器是否存在 if (parent != null) { // 如果父加载器存在则应用父加载器进行加载 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 } // 如果父加载器和BootstrapClassLoader都没有加载到的话 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); } // 返回已加载到的类或null return c; }}
加载步骤:
- 判断以后类加载器是否曾经加载指标类,如果曾经加载则返回,否则应用判断是否存在父加载器
- 如果父加载器存在反复步骤1,直到
parent == null
,这时会应用 BootstrapClassLoader 加载 - 如果父加载器和疏导类加载器都没有加载到指标类的话,就应用本人的
findClass()
办法来加载指标类。
找到某个ClassLoader,这里以AppClassLoader为例
static class AppClassLoader extends URLClassLoader {
在AppClassLoader中搜寻findClass()
办法是找不到的,所以找到每个加载器独特的父类URLClassLoader
,在这里能够看到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;}
findClass
办法的作用是从指定的门路找到传入的文件名称的.class文件,并返回回去,该办法的次要逻辑在run()
办法中。
4.实现本人的ClassLoader
从后面能够晓得,要想实现classLoader只须要继承ClassLoader类并重写 findClass
和 loadClass
办法即可。
4.1 创立java文件
在桌面创立了一个名为Test的Java文件,并应用javac命令进行编译。
public class Test { public void sayHi() { System.out.println("hi!"); } public static void main(String[] args) { System.out.println("Hello World!"); }}
4.2 实现办法重写与测试
/** * Created by luyi on 2021/2/16. * 形容:自定义ClassLoader */public class MyClassLoader extends ClassLoader { public String classPath; public MyClassLoader(String classPath) { this.classPath = classPath; } /** * 通过输出流读取文件 * * @param name 文件全限定类名 */ public byte[] loadByte(String name) throws IOException { if (name == null || name.length() == 0) { return new byte[1024]; } name = name.replaceAll("\\.", "/"); FileInputStream fis = new FileInputStream(classPath + "/" + name + ".class"); int len = fis.available(); byte[] data = new byte[len]; fis.read(data); fis.close(); return data; } /** * 将指定的门路中的指定的文件进行加载 * * @param name 全限定类名 * @return * @throws ClassNotFoundException */ @Override protected Class<?> findClass(String name) throws ClassNotFoundException { if (name == null || name.length() == 0) { return null; } try { byte[] data = loadByte(name); return defineClass(name, data, 0, data.length); } catch (IOException e) { e.printStackTrace(); throw new ClassNotFoundException(); } } public static void main(String[] args) throws Exception { MyClassLoader loader = new MyClassLoader("/Users/luyi/Desktop"); Class<?> clazz = loader.loadClass("Test", false); Object instance = clazz.newInstance(); Method sayHi = clazz.getDeclaredMethod("sayHi", null); sayHi.invoke(instance, null); System.out.println(clazz.getClassLoader().getClass().getName()); }}
运行后果:
从运行后果能够看出,自定义的类加载器曾经实现了对指定文件的加载,并正确的执行了办法。