彻底剖析JVM类加载机制 (3)

JVM默认使用Launcher的getClassLoader()方法返回的类加载器AppClassLoader的实例加载我们的应用程序。

// Launcher构造方法 public Launcher() { Launcher.ExtClassLoader var1; // 构造扩展类加载器,设置类加载器parent属性设为null。 var1 = Launcher.ExtClassLoader.getExtClassLoader(); // 构造应用类加载器,设置类加载器parent属性为扩展类加载器。 this.loader = Launcher.AppClassLoader.getAppClassLoader(var1); Thread.currentThread().setContextClassLoader(this.loader); // 权限校验代码.. } } 2.2 双亲委派模型

类加载器采用三层、双亲委派模型,类加载器的父子关系不是继承关系,而是组合关系。除了启动类加载器外,其他类加载器都是继承自ClassLoader类。

image

工作过程:类加载器收到类加载请求,首先判断类是否已经加载,如果未被加载,尝试将请求向上委派给父类加载器加载。当父类加载器无法完成加载任务,再由子类加载器尝试加载。

// ClassLoader protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException { synchronized (getClassLoadingLock(name)) { // 检查类是否已被加载 Class<?> c = findLoadedClass(name); if (c == null) { try { if (parent != null) { // 非启动类加载器 c = parent.loadClass(name, false); } else { // 启动类加载器 c = findBootstrapClassOrNull(name); } } catch (ClassNotFoundException e) { // 父类加载器无法加载指定类 } if (c == null) { // 调用当前类加载器的findClass方法进行类加载 c = findClass(name); } } if (resolve) { resolveClass(c); } return c; } } 为什么使用双亲委派模型,感觉走了弯路?

双亲委派模型下,类加载请求总会被委派给最上层的启动类加载器。对于未加载的类来说,需要从底层走到顶层;如果用户定义的类已经被加载过,则不需要委派过程。

使用双亲委派机制有下面几个好处:

沙箱安全机制,防止核心类库代码被篡改。

避免类重复加载,父类加载器加载过,子类加载器不需要再次加载。

全盘负责委托机制

全盘负责 :即是当一个classloader加载一个Class的时候,这个Class所依赖的和引用的其它Class 通常 也由这个classloader负责载入。 委托机制 :先让parent(父)类加载器 寻找,只有在parent找不到的时候才从自己的类路径中去寻找。

参考Launcher构造方法

Thread.currentThread().setContextClassLoader(this.loader); 自定义类加载器

自定义类加载器操作主要是继承ClassLoader类,重写上面源码中的findClass(name)方法。

public class CustomClassLoaderTest { static class CustomClassLoader extends ClassLoader { private String classFilePath; public CustomClassLoader(String classFilePath) { this.classFilePath = classFilePath; } // 载入class数据流 private byte[] loadClassFile(String name) throws Exception { name = name.replaceAll("\\.", "http://www.likecs.com/"); FileInputStream fis = new FileInputStream(classFilePath + "http://www.likecs.com/" + name + ".class"); int len = fis.available(); byte[] data = new byte[len]; fis.read(data); fis.close(); return data; } protected Class<?> findClass(String name) throws ClassNotFoundException{ try { byte[] data = loadClassFile(name); // 加载--链接--初始化等逻辑 return defineClass(name,data,0,data.length); } catch (Exception e) { throw new ClassNotFoundException(); } } } public static void main(String[] args) throws Exception { CustomClassLoader classLoader = new CustomClassLoader("F:"); Class<?> clazz = classLoader.loadClass("com.lzp.java.jvm.classloader.JVMTest"); Object instance = clazz.newInstance(); Method method = clazz.getDeclaredMethod("add", null); System.out.println(method.invoke(instance)); System.out.println(clazz.getClassLoader().getClass().getName()); } }

自定义类加载器的父加载器是应用类加载器。CustomClassLoader是使用AppClassLoader运行的,自然而然是父类加载器。

打破双亲委派机制

在一些场景下,打破双亲委派是必要的。例如Tomcat中可能有多个应用,引用了不同的Spring版本。打破双亲委派,可以实现应用隔离。

JVM使用loadClass方法实现双亲委派机制。重写loadClass方法,便可以打破双亲委派机制。

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/zgxjzg.html