最近被 一句话所触动——**种一棵树最好的时间是十年前,其次是如今。**因此决定要开始记录本身的学习之路。html
咱们都知道,每一个.java文件能够通过javac指令编译成.class文件,里面包含着java虚拟机的机器指令。当咱们须要使用一个java类时,虚拟机会加载它的.class文件,建立对应的java对象。将.class调入虚拟机的过程,称之为加载。java
loading :加载。经过类的彻底限定名找到.class字节码文件,同时建立一个对象。mysql
verification:验证。确保class字节码文件符合当前虚拟机的要求。c++
preparation:准备。这时候将static修饰的变量进行内存分配,同时设置初始值。sql
resolution:解析。虚拟机将常量池中的符号引用变为直接引用。数据库
initialization:初始化。类加载的最后阶段。若是这个类有超类,进行超类的初始化,执行类的静态代码块,同时给类的静态变量赋予初值。前面的preparation阶段是分配内存,都只是默认的值,并无被赋予初值。数组
类加载器的任务是将类的二进制字节流读入到JVM中,而后变成一个JVM能识别的class对象,同时实例化。因而咱们就能够分解ClassLoader的任务。ide
咱们查看源码,找到对应解决方案:post
在ClassLoader中,定义了两个接口:学习
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。
而代理模式 ,就是像上面图片所展现那样,当要加载一个类时,加载器会寻求其父类的帮助,让父类尝试去加载这个类。只有当父类失败后,才会由本身加载。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是经常使用的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实现类的加载。
给出例子,重写文件系统加载器
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] http://www.javashuo.com/article/p-zoasinon-cr.html
[3] https://juejin.im/post/5e1aaf626fb9a0301d11ac8e#heading-8