欢迎来到我的博客!

类动态加载

类动态加载

类加载与反序列化

反序列是利用的readObject()​方法重写,而类加载是为什么 ?

类加载

Java 程序在运行前需要先编译成 class文件​,Java 类初始化的时候会调用 java.lang.ClassLoader​ 加载类字节码,ClassLoader​ 会调用 JVM 的 native 方法(defineClass0/1/2​)来定义一个 java.lang.Class​ 实例

Javac

javac是用于将源码文件.java​编译成对应的字节码文件.class

具体实现步骤 : 源码 -> 词法分析器组件(生成token流)-> 词法分析器组件(语法树)-> 语义分析器组件(注解语法树)-> 代码生成器组件(字节码)

类加载过程

先在方法区找.class​信息,有的话直接调用,没有的话则使用类加载器加载到方法区(静态成员放在静态区,非静态成员放在非静态区)静态代码快在类加载时自动执行代码,非静态的不执行,先父类后子类,先静态后飞静态,静态方法和飞静态方法都是被动调用(不调就不执行)

​​​当运行某个类的时候,如果发现这个类还没有被加载,那么就会如上过程进行加载

Java 程序是由 class 文件组成的一个完整的应用程序,在程序运行时,并不会一次性加载所有的 class 文件进入内存,而是通过 Java 的类加载机制(ClassLoader)进行动态加载,从而转换成 java.lang.Class 类的一个实例

动态加载

Java 类加载方式分为 显式​ 和 隐式

  1. 显式​ 即我们通常使用 Java反射​ 或者 ClassLoader​ 来动态加载一个类对象
  2. 隐式​ 指的是 类名.方法名()​ 或 new​ 类实例

显式​ 类加载方式也可以理解为类动态加载,可以自定义类加载器去加载任意的类

Class.forname加载

复制代码
public class main {
    public static void main(String[] args) throws ClassNotFoundException {
        Class.forName("Persion");
        /*
        这是静态代码快
        */
    }
}

我们发现,当使用Class.forName()​调用时,也会进行初始化,可以查看一下底层代码实现

它底层调用了一个forName0方法

它的forName0方法是一个native方法,它是用C或C++实现的,但是上方还存在一个forName的重载方法,它就可以判断是否需要初始化(存在三个参数,第一个是类,第二个是否初始化,第三个是类加载器)

复制代码
public class main {
    public static void main(String[] args) throws ClassNotFoundException {
        Class<?> c = Class.forName("Persion", false, ClassLoader.getSystemClassLoader()); // 此时就不会在初始化了 这里使用的是系统类加载器
		// c.newInstance(); // 这是初始化操作
    }
}

ClassLoader

通过ClassLoader​来实现类加载

复制代码
public class main {
    public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException {
        ClassLoader cl = ClassLoader.getSystemClassLoader();
        Class<?> aClass = cl.loadClass("Persion");
        aClass.newInstance();
    }
}

这里通过aClass.newInstance()​​来实现类初始化,那么可以考虑的一个问题是能否通过在别的地方写的一些类,然后通过ClassLoader来加载,那么这就是可以执行任意类(比反射实现的效果还要多,反射在一些私有的是不允许调用的)

类加载器的种类

Bootstarp ClassLoader

启动类加载器 : 这个类加载器负责将一些核心的、被JVM识别的类加载进来,用C++实现,与JVM一体的**,**用于加载一些java.lang 目录下的是无法直接获取到的

Extension ClassLoader

扩展类加载器 : 这个类加载器用来加载 Java 的扩展库,其目录在jre\lib\ext

Application ClassLoader

App类加载器/系统类加载器 : 用于加载当前目录下我们自己定义编写的类

User ClassLoader

用户自己实现的加载器 : 当实际需要自己掌控类加载过程时才会用到,一般没有用到

双亲委托机制

如果一个类加载器收到了类加载的请求,首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器去完成,每一层次的类加载器都是如此,因此所有的加载请求最终都应该传送到顶层的启动类加载器中,只有当父加载器反馈自己无法完成这个加载请求时(它的搜索范围中没有找到所需要的类)子加载器才会自己去加载

  1. 首先,检查请求的类是否已经加载过了
  2. 未加载,则请求父类加载器去加载对应路径下的类
  3. 如果加载不到,才由下面的子类依次去加载

java.lang.String -> 本地加载器 -> 扩展加载器 -> 根(顶层)加载器

类加载机制

  1. 在调用loadClass处打入断点

可以看到它的默认类加载器是AppClassLoader

  1. 由于传入的是一个参数,它会调用到本应该调用的是Launcher类中的loadClass方法,但是它斌没有接收一个参数的loadClass方法,就会去调用父类的loadClass方法

  1. ClassLoader​中的loadClass(String name)​会返回一个两个参数的loadClass方法

  1. 之后将两个参数的返回给子类(Launcher类)

  1. 子类执行完成后,又会调用父类的loadClass方法

​​

这里就相当于是执行了双亲委派的流程

先会判断是否是ExtClassLoader​的父属性,如果是的话就回去指定它的loadClass方法,很明显这里是会进入ExtClassLoader​类中执行它的loadClass

但是通过搜索会发现它并不存在loadClass方法,去父类找URLClassLoader​中找,它又会去调用它的父类loadClass方法

又回到了ClassLoader.loadClass()

但是此时的加载类已经变成了ExtClassLoader

  1. 继续往下执行,当它再次判断时,由于它类加载的父属性是BootstropClassLoader​,它是由C编写的,在java中是null,所以不会在进入执行else

之后又会去findBootstrapClassOrNull()中寻找,还是为false

  1. 之后继续执行findClass(),而它是一个重写的方法,正常在子类中执行

而当前的加载类还是ExtClassLoader​,但是它也没有该方法,只能去它对应的父类URLClassLoader​中找

​​

因为这里使用的是ExtClassLoader​,它是用来加载java的扩展库,所以这里是找不到的,为null,到此ExtClassLoader​执行完成,退回ApplicationClassLoader

  1. 程序退回到ClassLoader.loadClass()判断处,继续往下执行

这里就是以ApplicationClassLoader​身份去执行findClass

  1. 由于ApplicationClassLoader​ 用于加载我们自己定义编写的类,所以这里肯定是可以找到的

​ucp是一个URLClassPath类,通过它在当前的ClassLoader加载路径中找

  1. 通过upc.getResource()​找到之后会再去调用一个defindClass()

​​image​​

URLClassLoader​类重写了defineClass()​最终还是调用了一个多参数的defindClass()

  1. 继续执行查看,发现会调到SecureClassLoader​类中,而它是URLClassLoader​的父类

它也返回了一个调用defineClass()

  1. 继续追踪,查看其最终落脚

最终在ClassLoader​中的defineClass​停止,它又执行了一个defineClass1()​,它的definClass()方法中可以看到存在5个参数,第一个是类名、第二个参数是字节码、第三个参数是起始位置、第四个是长度、第五个是保护域

defineClass1​是一个native类型,最终通过它来完成整个类的加载

任意类加载

ClassLoader.defineClass

复制代码
import main.Test;


import java.lang.reflect.Method;

import java.net.URL;

import java.net.URLClassLoader;

import java.nio.file.Files;

import java.nio.file.Paths;

import java.security.ProtectionDomain;




public class classLoaderTest {

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

ClassLoader cl = ClassLoader.getSystemClassLoader();



    Method defineClassLoader = ClassLoader.class.getDeclaredMethod(&quot;defineClass&quot;, String.class, byte[].class, int.class, int.class); // 反射获取defineClass方法
    defineClassLoader.setAccessible(true); // 关闭安全检测
    byte[] code = Files.readAllBytes(Paths.get(&quot;H:\\tmp\\ss.class&quot;)); // 将class转为字节
    Class c = (Class) defineClassLoader.invoke(cl, &quot;ss&quot;, code, 0, code.length); // 加载类
    c.newInstance(); // 创建类
}


    Method defineClassLoader = ClassLoader.class.getDeclaredMethod(&quot;defineClass&quot;, String.class, byte[].class, int.class, int.class); // 反射获取defineClass方法
    defineClassLoader.setAccessible(true); // 关闭安全检测
    byte[] code = Files.readAllBytes(Paths.get(&quot;H:\\tmp\\ss.class&quot;)); // 将class转为字节
    Class c = (Class) defineClassLoader.invoke(cl, &quot;ss&quot;, code, 0, code.length); // 加载类
    c.newInstance(); // 创建类
}
}

URLClassLoader

可使用jar/file/http​协议进行加载类

复制代码
import main.Test;


import java.net.URL;

import java.net.URLClassLoader;




public class classLoaderTest {

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

URLClassLoader file = new URLClassLoader(new URL[]{new URL("file:///H:\tmp\")}); // 通过file协议

Class<?> aClass = file.loadClass("ss");

aClass.newInstance();



	// 使用file协议
    URLClassLoader file = new URLClassLoader(new URL[]{new URL(&quot;file:///H:\\tmp\\&quot;)}); // 通过file协议
    Class&lt;?&gt; aClass = file.loadClass(&quot;ss&quot;);
    aClass.newInstance();

	// 使用http协议
    URLClassLoader http = new URLClassLoader(new URL[]{new URL(&quot;http://localhost:80/&quot;)});
    Class&lt;?&gt; aClass = http.loadClass(&quot;ss&quot;);
    aClass.newInstance();

	//使用jar协议

}


	// 使用file协议
    URLClassLoader file = new URLClassLoader(new URL[]{new URL(&quot;file:///H:\\tmp\\&quot;)}); // 通过file协议
    Class&lt;?&gt; aClass = file.loadClass(&quot;ss&quot;);
    aClass.newInstance();

	// 使用http协议
    URLClassLoader http = new URLClassLoader(new URL[]{new URL(&quot;http://localhost:80/&quot;)});
    Class&lt;?&gt; aClass = http.loadClass(&quot;ss&quot;);
    aClass.newInstance();

	//使用jar协议

}
}

Unsafe.defineClass

复制代码
import main.Test;


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;

import java.security.ProtectionDomain;




public class classLoaderTest {

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



    ClassLoader cl = ClassLoader.getSystemClassLoader();

    byte[] code = Files.readAllBytes(Paths.get(&quot;H:\\tmp\\ss.class&quot;));

    Class c = Unsafe.class;
    Field theUnsafe = c.getDeclaredField(&quot;theUnsafe&quot;);
    theUnsafe.setAccessible(true);
    Unsafe unsafe = (Unsafe) theUnsafe.get(null);
    Class c2 = (Class) unsafe.defineClass(&quot;ss&quot;, code, 0, code.length,cl,null);
    c2.newInstance();
}


    ClassLoader cl = ClassLoader.getSystemClassLoader();

    byte[] code = Files.readAllBytes(Paths.get(&quot;H:\\tmp\\ss.class&quot;));

    Class c = Unsafe.class;
    Field theUnsafe = c.getDeclaredField(&quot;theUnsafe&quot;);
    theUnsafe.setAccessible(true);
    Unsafe unsafe = (Unsafe) theUnsafe.get(null);
    Class c2 = (Class) unsafe.defineClass(&quot;ss&quot;, code, 0, code.length,cl,null);
    c2.newInstance();
}
}

posted @   余星酒  阅读(19)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· AI 智能体引爆开源社区「GitHub 热点速览」
· 从HTTP原因短语缺失研究HTTP/2和HTTP/3的设计差异
· 三行代码完成国际化适配,妙~啊~
点击右上角即可分享
微信分享提示