• Java类加载器详解


    1 特点

    双亲委派:

    如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器去完成,每一个层次的类加载器都是如此,因此所有的加载请求最终都应该传送到顶层的启动类加载器中,只有当父加载器反馈自己无法完成这个加载请求(它的搜索范围中没有找到所需的类)时,子加载器才会尝试自己去加载。

    负责依赖:

    同时加载它依赖的类

    缓存加载:

    加载后放到内存里,再次加载时就会直接获取

    2 应用

    类层次划分,osgi,热部署,代码加密

    3 分类

    3.1 启动类加载器 Bootstrap Classloader

    负责将存放在 JAVA_HOME/lib 目录的,或者被 -Xbootclasspath 参数所指定的路径中的,并且是虚拟机识别的类库加载到虚拟机内存中。

     BootstrapClassLoader 对 Java程序是不可见的,所以获取时返回了 null,我们也可以通过某一个类的加载器是否为 null 来作为判断该类是不是使用 BootstrapClassLoader 进行加载的依据

    3.2 扩展类加载器 Extension ClassLoader

    sun.misc.Launcher$ExtClassLoader ,负责加载 JAVA_HOME\lib\ext,java.ext.dirs 系统变量所指定的路径中的所有类库,开发者可以直接使用扩展类加载器。

    3.3 应用程序类加载器 Application ClassLoader

    sun.misc.Launcher$AppClassLoader, 负责加载用户类路径上所指定的类库,可以直接使用这个类加载器。

    3.4 系统类加载器system class loader

    默认是应用程序类加载器

     系统类加载器配置:

    使用如下配置,可以将自定义的类加载器配置成系统类加载器

    -Djava.system.class.loader=me.capthua.advancedjava.week1.classloader.MyClassLoader

    此时,自定义类必须实现如下的构造方法

    1. public MyClassLoader(ClassLoader classLoader){
    2. super(classLoader);
    3. }

    3.5 自定义类加载器

    示例代码如下:

    1. package me.ffulauh.javalang;
    2. import java.io.IOException;
    3. import java.io.InputStream;
    4. public class ClassLoaderTest {
    5. public static void main(String[] args)throws Exception {
    6. ClassLoader myLoader=new ClassLoader() {
    7. @Override
    8. public Class loadClass(String name) throws ClassNotFoundException {
    9. try {
    10. String fileName=name.substring(name.lastIndexOf(".")+1)+".class";
    11. InputStream is=getClass().getResourceAsStream(fileName);
    12. if(is==null){
    13. return super.loadClass(name);
    14. }
    15. byte[] b=new byte[is.available()];
    16. is.read(b);
    17. return defineClass(name,b,0,b.length);
    18. } catch (IOException e){
    19. throw new ClassNotFoundException(name);
    20. }
    21. }
    22. };
    23. Object obj=myLoader.loadClass("me.ffulauh.javalang.ClassLoaderTest").newInstance();
    24. System.out.println(obj.getClass());
    25. //class me.ffulauh.javalang.ClassLoaderTest
    26. System.out.println(obj.getClass().getClassLoader());
    27. //me.ffulauh.javalang.ClassLoaderTest$1@d716361
    28. System.out.println(me.ffulauh.javalang.ClassLoaderTest.class.getClassLoader());
    29. //sun.misc.Launcher$AppClassLoader@18b4aac2
    30. System.out.println(obj instanceof me.ffulauh.javalang.ClassLoaderTest);
    31. //false
    32. }
    33. }

    上面自定义的类加载器的逻辑是:从资源文件中读入一个字节码文件的输入流,如果流为空,则让父 类加载器加载,否则,由自定义的类加载器加载。

    此逻辑为 子 类加载器 优先加载。不属于双亲委派机制。当然这里的逻辑有开发者自定定义。也可以显然 父 类加载器加载。

    自定义类加载器的 父 类加载器是应用程序类加载器:

    4 类加载器源码分析

    4.1 ClassLoader

    ClassLoader中的主要方法为:loadClass(String name, boolean resolve) 方法中 resolve 表示是否连接,默认为 false

    先检查是否已经被加载过,若没有加载则调用父加载器的loadClass(),若父加载器为空则默认使用启动类加载器作为父加载器。如果父加载器加载失败,抛出ClassNotFoundException异常后,再调用自己的findClass()方法进行加载。findClass()需要子类实现。

    1. ClassLoader.java
    2. protected Class loadClass(String name, boolean resolve)
    3. throws ClassNotFoundException
    4. {
    5. synchronized (getClassLoadingLock(name)) {
    6. // First, check if the class has already been loaded
    7. Class c = findLoadedClass(name);
    8. if (c == null) {
    9. long t0 = System.nanoTime();
    10. try {
    11. if (parent != null) {
    12. c = parent.loadClass(name, false);
    13. } else {
    14. c = findBootstrapClassOrNull(name);
    15. }
    16. } catch (ClassNotFoundException e) {
    17. // ClassNotFoundException thrown if class not found
    18. // from the non-null parent class loader
    19. }
    20. if (c == null) {
    21. // If still not found, then invoke findClass in order
    22. // to find the class.
    23. long t1 = System.nanoTime();
    24. c = findClass(name);
    25. // this is the defining class loader; record the stats
    26. sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
    27. sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
    28. sun.misc.PerfCounter.getFindClasses().increment();
    29. }
    30. }
    31. if (resolve) {
    32. resolveClass(c);
    33. }
    34. return c;
    35. }
    36. }
    37. protected Class findClass(String name) throws ClassNotFoundException {
    38. throw new ClassNotFoundException(name);
    39. }

    4.2 URLClassLoader

    AppClassLoader,ExtClassLoader都继承URLClassLoader。

    URLClassLoader 中的 findClass 调用的是 ClassLoader中的defineClass(),defineClass 中会调用 SecureClassLoader 中的 defineClass方法。

    1. URLClassLoader.java
    2. protected Class findClass(final String name)
    3. throws ClassNotFoundException
    4. {
    5. ......
    6. return defineClass(name, res);
    7. ......
    8. }
    9. private Class defineClass(String name, Resource res) throws IOException {
    10. ......
    11. return defineClass(name, b, 0, b.length, cs);
    12. ......
    13. }
    14. SecureClassLoader.java
    15. protected final Class defineClass(String name,
    16. byte[] b, int off, int len,
    17. CodeSource cs)
    18. {
    19. return defineClass(name, b, off, len, getProtectionDomain(cs));
    20. }

    ClassLoader中的的 addURL(URL url) 

    这个方法是将特定的 url 添加到类加载器,这些 url 是用来搜索类与资源的。将类注入现有累加载器是会用到这个方法。

    1. /**
    2. * Appends the specified URL to the list of URLs to search for classes and resources.
    3. * If the URL specified is null or is already in the list of URLs, or if this loader is closed, then invoking this method has no effect.
    4. */
    5. protected void addURL(URL url) {
    6. ucp.addURL(url);
    7. }

    5.添加引用类的几种方式

    1. 将类放到指定目录

    放到 JDK 的 JAVA_HOME/lib/ext 下, -Djava.ext.dirs 指定目录, java -cp/classpath 指定类路径

    2. 自定义 ClassLoader 加载

    3. 将类注入已有的ClassLoader。拿到当前执行类的 ClassLoader,反射调用 defineClass 或者 addUrl方法 让已有的类加载器加载新类。

    代码如下:

    1. ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
    2. String byteFilePath = "E:\\ideaProjects\\tech4u\\src\\main\\java\\me\\ffulauh\\javalang\\jvm\\classloader" +
    3. "\\HelloWorld.class";
    4. File byteFile=new File(byteFilePath);
    5. byte[] classBytes= FileUtils.file2byte(byteFile);
    6. String className="me.ffulauh.javalang.jvm.classloader.HelloWorld";
    7. Method defineMethod = ClassLoader.class.getDeclaredMethod("defineClass",String.class,byte[].class,int.class,int.class);
    8. defineMethod.setAccessible(true);
    9. Class helloClazz =(Class) defineMethod.invoke(classLoader,className, classBytes, 0, classBytes.length);
    10. Method method=helloClazz.getMethod("sayHello");
    11. method.invoke(helloClazz.newInstance());
    12. String jarFile="D:\\devtools\\repo\\com\\alibaba\\druid\\1.2.6\\druid-1.2.6.jar";
    13. Class jarClazz=null;
    14. Method addUrlMethod= URLClassLoader.class.getDeclaredMethod("addURL", URL.class);
    15. addUrlMethod.setAccessible(true);
    16. addUrlMethod.invoke(classLoader,new File(jarFile).toURI().toURL());
    17. jarClazz=Class.forName("com.alibaba.druid.pool.DruidPooledStatement");
    18. System.out.println(jarClazz);

    6 遇到的问题

    1. 如果一个类加载器已经加载了一个类,它再次调用defineClass()方法加载这个类时,会抛异常

    1. Exception in thread "main" java.lang.LinkageError: loader (instance of me/ffulauh/javalang/opbytecode/asm/simple/MyClassLoader): attempted duplicate class definition for name: "me/ffulauh/javalang/opbytecode/asm/HelloWorld"
    2. at java.lang.ClassLoader.defineClass1(Native Method)
    3. at java.lang.ClassLoader.defineClass(ClassLoader.java:757)
    4. at java.lang.ClassLoader.defineClass(ClassLoader.java:636)
    5. at me.ffulauh.javalang.opbytecode.asm.simple.MyClassLoader.findClass(MyClassLoader.java:28)
    6. at me.ffulauh.javalang.opbytecode.asm.simple.MyClassLoader.loadClass(MyClassLoader.java:20)
    7. at me.ffulauh.javalang.opbytecode.asm.transform.ModifyMethodDemo.main(ModifyMethodDemo.java:38)

    2. 在Tomcat 8中,处理请求的线程对应的类加载器为 TomcatEmbeddedWebappClassLoader,这个类加载器的 addURL方法是没有实现的。此时如果调用这个方法,是不会加载不到类的。

  • 相关阅读:
    CSS 元素的显示与隐藏
    对于L1正则化和L2正则化的理解
    【100天精通Python】Day72:Python可视化_一文掌握Seaborn库的使用《二》_分类数据可视化,线性模型和参数拟合的可视化,示例+代码
    gradle 常用命令
    面试官:什么是三色标记
    linux同步搭建多台服务器
    python 列表去重的5种方式
    注册树模式
    Q-M(Quine-McCluskey)两级逻辑化简算法原理解析
    ubuntu 安装 mariadb,如何创建用户,并远程连接
  • 原文地址:https://blog.csdn.net/CaptHua/article/details/126509456