JAVA学习之ClassLoader

JAVA学习之ClassLoader

前言

最近被 一句话所触动——种一棵树最好的时间是十年前,其次是现在。所以决定要开始记录自己的学习之路。

什么是类加载?

我们都知道,每个.java文件可以经过javac指令编译成.class文件,里面包含着java虚拟机的机器指令。当我们需要使用一个java类时,虚拟机会加载它的.class文件,创建对应的java对象。将.class调入虚拟机的过程,称之为加载。

loading :加载。通过类的完全限定名找到.class字节码文件,同时创建一个对象。

verification:验证。确保class字节码文件符合当前虚拟机的要求。

preparation:准备。这时候将static修饰的变量进行内存分配,同时设置初始值。

resolution:解析。虚拟机将常量池中的符号引用变为直接引用。

  • 符号引用:在编译之后完成的,一个常量并没有进行内存分配,也就只能用符号引用。
  • 直接引用:常量会在preparation阶段将常量进行内存分配,于是就可以建立直接的虚拟机内存联系,就可以直接引用。

initialization:初始化。类加载的最后阶段。如果这个类有超类,进行超类的初始化,执行类的静态代码块,同时给类的静态变量赋予初值。前面的preparation阶段是分配内存,都只是默认的值,并没有被赋予初值。

类加载在java中有两种方式

  • 显示加载。通过class.fornNme(classname)或者this.getClass().getClassLoader.loadClass(classname)加载。即通过调用类加载器classLoader来完成类的加载
  • 隐式加载。类在需要被使用时,不直接调用ClassLoader,而是虚拟机自动调用ClassLoader加载到内存中。

什么是ClassLoader?

类加载器的任务是将类的二进制字节流读入到JVM中,然后变成一个JVM能识别的class对象,同时实例化。于是我们就可以分解ClassLoader的任务。

  1. 找到类的二进制字节流,并加载进来
  2. 规则化为JVM能识别的class对象

我们查看源码,找到对应解决方案:

在ClassLoader中,定义了两个接口:

  1. findClass(String name).
  2. defineClass(byte[] b , int off , int len)

findClass用于找到二进制文件并读入,调用defineClass用字节流变成JVM能识别的class对象,同时实例化class对象。

我们来看个例子:

protected Class<?> findClass(String name) throws ClassNotFoundException {
	  // 获取类的字节数组,通过name找到类,如果你对字节码加密,需要自己解密出来
      byte[] classData = getClassData(name);
      if (classData == null) {
          throw new ClassNotFoundException();
      } else {
	      //使用defineClass生成class对象
          return defineClass(name, classData, 0, classData.length);
      }
  }

代理模式

提到类加载器,一定得涉及的是委派模式。在JAVA中,ClassLoader存在一下几类,他们的关系如下:

  • Bootstrap ClassLoader:引导类加载器。采用原生c++实现,用于加载java的核心类(%JAVA_HOME%/lib路径下的核心类库或者 -Xbootclasspath指定下的jar包)到内存中。没有父类
  • Extension ClassLoader:扩展类加载器。java实现,加载/lib/ext目录下或者由系统变量-Djava.ext.dir指定位路径中的类库。父类加载器为null。
  • System ClassLoader:它会根据java应用的类路径(CLASSPATH)来加载类,即java -classpath-D java.class.path 指定路径下的类库,也就是我们经常用到的classpath路径。一般来说,java应用的类都是由它完成。可以由ClassLoader.getSystemLoader()方法获得。父类加载器是Extension ClassLoader。
  • Customize ClassLoader:用户自定义加载器。用于完成用户自身的特有需求。父类加载器为System ClassLoader。
    1. 加载不在CLASSPATH路径的class文件
    2. 加密后的class文件需要用户自定义ClassLoader来解密后才能被JVM识别。
    3. 热部署,同一个class文件通过不同的类加载器产生不同的class对象。两个类完全相同不仅仅要是同一个class文件,还需要同一个类加载器、同一个JVM。

    代理模式 ,就是像上面图片所展示那样,当要加载一个类时,加载器会寻求其父类的帮助,让父类尝试去加载这个类。只有当父类失败后,才会由自己加载。ClassLoader的loadClass()方法中体现。

    示例代码:

    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 {
                      //如果没有父类,则委托给启动加载器去加载
                          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
                      // 如果都没有找到,则通过自定义实现的findClass去查找并加载
                      c = findClass(name);
    
                  }
              }
              if (resolve) {//是否需要在加载时进行解析
                  resolveClass(c);
              }
              return c;
          }
      }
    
    

    findClass(String name)的类似实现上面有示例,resolveClass()就是完成解析功能。

    URLClassLoader

    URLClassLoader是常用的ClassLoader类,其实现了findclass()接口,所以如果自定义时继承URLClassLoader可以不用重写findclass()。ExtClassLoader在代理模式中属于Extension ClassLoader,而AppClassLoader属于System ClassLoader。

    线程上下文加载器

    前面我们提到过BootStrap ClassLoader加载的是%JAVA_HOME%/lib下的核心库文件,而CLASSPATH路径下的库由System ClassLoader加载。但在java语言中,存在这种现象,Java 提供了很多服务提供者接口(Service Provider Interface,SPI),允许第三方为这些接口提供实现,如JDBC、JCE、JNDI等。而SPI是Java的核心库文件,由 BootStrap ClassLoader加载,第三方实现是放在CLASSPATH路径下,由System ClassLoader加载。当BootStrap ClassLoader启用时,需要加载其实现,但自己找不到,又因为代理模式的存在,无法委托System ClassLoader来加载,所以无法实现。

    Contex ClassLoader(线程上下文加载器)刚好可以解决这个问题。

    Contex ClassLoader(线程上下文加载器)是从 JDK 1.2 开始引入的。类 java.lang.Thread中的方法 getContextClassLoader()setContextClassLoader(ClassLoader cl)用来获取和设置线程的上下文类加载器。如果没有通过 setContextClassLoader(ClassLoader cl)方法进行设置的话,线程将继承其父线程的上下文类加载器。Java 应用运行的初始线程的上下文类加载器是系统类加载器。在线程中运行的代码可以通过此类加载器来加载类和资源。

    例子JDBC:

    public class DriverManager {
    	//省略......
        static {
            loadInitialDrivers();
            println("JDBC DriverManager initialized");
        }
    
     private static void loadInitialDrivers() {
         sun.misc.Providers()
         AccessController.doPrivileged(new PrivilegedAction<Void>() {
                public Void run() {
                    ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);
                  //省略不必要的代码......
                }
            });
        }
    public class Driver extends com.mysql.cj.jdbc.Driver {
        public Driver() throws SQLException {
            super();
        }
    
        static {
          //省略
        }
    }
    public static <S> ServiceLoader<S> load(Class<S> service) {
    	 //通过线程上下文类加载器加载
          ClassLoader cl = Thread.currentThread().getContextClassLoader();
          return ServiceLoader.load(service, cl);
      }
    //调用
    String url = "jdbc:mysql://localhost:3342/cm-storylocker?characterEncoding=UTF-8";
    // 通过java库获取数据库连接
    Connection conn = java.sql.DriverManager.getConnection(url, "root", "password");
    
    

    我们可以看到,当我们在使用JDBC的时候,会使用有DriverManager类,它的static代码区引用的ServiceLoader类会完成JDBC实现类的加载。

    如何设计自己的ClassLoader

    给出例子,重写文件系统加载器

    public class FileSystemClassLoader extends ClassLoader { 
    
       private String rootDir; 
    
       public FileSystemClassLoader(String rootDir) {
           this.rootDir = rootDir;
       } 
    
       protected Class<?> findClass(String name) throws ClassNotFoundException {
           //根据规则获取字节流数组
           byte[] classData = getClassData(name);
           if (classData == null) {
               throw new ClassNotFoundException();
           }
           else {
               return defineClass(name, classData, 0, classData.length);
           }
       }
     //设定自己的读取规则
       private byte[] getClassData(String className) {
           String path = classNameToPath(className);
           try {
               InputStream ins = new FileInputStream(path);
               ByteArrayOutputStream baos = new ByteArrayOutputStream();
               int bufferSize = 4096;
               byte[] buffer = new byte[bufferSize];
               int bytesNumRead = 0;
               while ((bytesNumRead = ins.read(buffer)) != -1) {
                   baos.write(buffer, 0, bytesNumRead);
               }
               return baos.toByteArray();
           } catch (IOException e) {
               e.printStackTrace();
           }
           return null;
       } 
    
       private String classNameToPath(String className) {
           return rootDir + File.separatorChar
                   + className.replace(‘.‘, File.separatorChar) + ".class";
       }
    }
    

    本文学习参考自

    [1] https://www.ibm.com/developerworks/cn/java/j-lo-classloader/index.html#minor1.1

    [2] https://blog.csdn.net/javazejian/article/details/73413292

    [3] https://juejin.im/post/5e1aaf626fb9a0301d11ac8e#heading-8

原文地址:https://www.cnblogs.com/waaaafool/p/12578914.html

时间: 2024-10-10 04:20:29

JAVA学习之ClassLoader的相关文章

java类加载器——ClassLoader

Java的设计初衷是主要面向嵌入式领域,对于自定义的一些类,考虑使用依需求加载原则,即在程序使用到时才加载类,节省内存消耗,这时即可通过类加载器来动态加载. 如果你平时只是做web开发,那应该很少会跟类加载器打交道,但如果你想深入学习tomcat服务器的架构,它是必不可少的.所谓类加载器,就是用于加载Java类到Java虚拟机中,它负责读取Java字节码,并转换成java.lang.Class类的一个实例,使字节代码.class文件得以运行.一般类加载器负责根据一个指定的类找到对应的字节代码,然

浅析java类加载器ClassLoader

作为一枚java猿,了解类加载器是有必要的,无论是针对面试还是自我学习. 本文从JDK提供的ClassLoader.委托模型以及如何编写自定义的ClassLoader三方面对ClassLoader做一个简要的总结. JDK中提供的ClassLoader 1. Bootstrap ClassLoader Bootstrap加载器是用C++语言写的,它是在Java虚拟机启动后初始化的,它主要负责加载%JAVA_HOME%/jre/lib以及%JAVA_HOME%/jre/classes中的类,是最顶

java学习笔记14--动态代理

java学习笔记14--动态代理 InvocationHandler接口 [java] view plaincopy public interface InvocationHandler{ public Object invoke(Object proxy,Method method,Object[] args)throws Throwable } 参数说明: Object  proxy:被代理的对象 Method  method:要调用的方法 Object   args[]:方法调用时所需要的

JAVA学习篇--Java类加载

由来: 与普通程序不同的是,Java程序(class文件)并不是本地的可执行程序(解释性语言).当运行Java程序时,首先运行JVM(Java虚拟机),然后再把Javaclass加载到JVM里头运行,负责加载Javaclass的这部分就ClassLoader.中文叫做类加载器. 类加载器就好比一个代理,你需要什么,我通过类加载器将你需要的内容返回给你! 类加载器有什么作用? 当程序需要的某个类,那么需要通过类加载器把类的二进制加载到内存中. 解释: 类加载器也是Java类,因为其他是java类的

JAVA学习篇--静态代理VS动态代理

本篇博客的由来,之前我们学习大话设计,就了解了代理模式,但为什么还要说呢? 原因: 1,通过DRP这个项目,了解到了动态代理,认识到我们之前一直使用的都是静态代理,那么动态代理又有什么好处呢?它们二者的区别是什么呢? 2,通过学习动态代理了解到动态代理是一种符合AOP设计思想的技术,那么什么又是AOP? 下面是我对它们的理解! 代理Proxy: Proxy代理模式是一种结构型设计模式,主要解决的问题是:在直接访问对象时带来的问题 代理是一种常用的设计模式,其目的就是为其他对象提供一个代理以控制对

Java学习笔记——动态代理

所谓动态,也就是说这个东西是可变的,或者说不是一生下来就有的.提到动态就不得不说静态,静态代理,个人觉得是指一个代理在程序中是事先写好的,不能变的,就像上一篇"Java学习笔记--RMI"中的远程代理,其中客户端服务对象就是一个远程服务对象的代理,这个代理可以使得客户在操作时感觉像在操作本地对象一样,远程对象对于客户是透明的.我们可以看出这里的远程代理,是在程序中事先写好的,而本节我们要讨论的远程代理,是由JVM根据反射机制,在程序运行时动态生成的.(以上是本人的理解,如果有不正确的地

【JAVA学习】单例模式的七种写法

尊重版权:http://cantellow.iteye.com/blog/838473 第一种(懒汉,线程不安全): Java代码   public class Singleton { private static Singleton instance; private Singleton (){} public static Singleton getInstance() { if (instance == null) { instance = new Singleton(); } retur

Tomcat学习之ClassLoader

Tomcat学习之ClassLoader 2012-09-04 22:19 8993人阅读 评论(4) 收藏 举报  分类: WEB服务器(13)  版权声明:本文为博主原创文章,未经博主允许不得转载. 目录(?)[+] 类装载器 JDK中提供了3种不同的类加载器:启动类装载器,扩展类装载器和系统类装载器.引导类装载器,用于引导启动Java虚拟机,当执行一个JAVA程序时,就会启动引导类装载器,它是使用本地代码来实现的,会装载%JAVA_HOME%\\jre\lib\rt.jar,它是所有类装载

【正文】Java类加载器( CLassLoader ) 死磕 4: 神秘的双亲委托机制

[正文]Java类加载器(  CLassLoader ) 死磕4:  神秘的双亲委托机制 本小节目录 4.1. 每个类加载器都有一个parent父加载器 4.2. 类加载器之间的层次关系 4.3. 类的加载次序 4.4 双亲委托机制原理与沙箱机制 4.5. forName方法和loadClass方法的关系 4.6. 使用组合而不用继承 4.7. 各种不同的类加载途径 4.1.每个类加载器都有一个parent父加载器 每个类加载器都有一个parent父加载器,比如加载SystemConfig.cl