类加载器简介
我们知道java源文件在运行前会被编译成class文件,class文件中存放着编译后的JVM虚拟机指令的二进制字节流。当一个类被使用时,JVM会加载它并在内存中创建相应的类对象。这个过程称为类加载。
类加载过程
流程如下:
image.png加载阶段通过类的完全限定名称查找该类的字节码文件,并使用该字节码文件创建Class 对象。链接阶段负责将类的二进制数据合并到JRE中。
可以分为以下三个阶段:
1.验证阶段
用于保证Class文件中包含的字节流信息满足当前Java虚拟机的要求。
2、准备阶段
为static修饰的静态类变量分配内存并设置为默认值;
用final修饰符修饰的静态变量不会被分配和初始化,因为此类变量会在编译时分配到内存空间;
实例变量不会被分配和初始化,因为实例变量是随Java堆中的对象分配的,而不是Java方法区;
3、分析阶段
将类的二进制数据中的符号引用转换为直接引用。初始化阶段在类加载的最后阶段,如果类有父类,会先初始化父类,执行静态变量赋值和静态代码块代码,同时也会初始化成员变量。
类加载器
类加载器可分为两种类型:
一种是Java虚拟机自带的类加载器,分别为启动类加载器、扩展类加载器、系统类加载器。启动类加载器(Bootstrap ClassLoader)底层由C/C++实现,不继承java.lang.ClassLoader类,无父类加载器;公共静态无效主(字符串[] args){
ClassLoader c1=Object.class.getClassLoader();
System.out.println(c1); //加载器打印的结果为null
负责加载%JAVA_HOME%/jre/lib目录下的核心类库,如rt.jar、charsets.jar等;负责加载%JAVA_HOME%/jre/classes中的类;扩展类加载器(Extensions ClassLoader)加载Java的扩展列库,默认加载%JAVA_HOME%/jre/lib/ext扩展目录下的类库或者java.ext.dirs系统变量指定的目录下的类库;系统类加载器的父类是扩展类加载器,扩展类加载器控制器的父类是启动类加载器;系统类加载器(App ClassLoader)负责加载ClassPath环境变量或系统属性java.class.path下的所有类库,主要加载开发者编写的类库; Java 程序的默认类加载器;是用户自定义的任何类加载器的默认父类加载器;一种是用户自定义的类加载器,是java.lang.ClassLoader子类实例。加载用户自定义路径下的类包,是通过继承java.lang.ClassLoader类来实现的。即父类是系统类加载器。
双亲委派机制
通过上面的学习,我们知道JVM默认有3个类加载器。除了根类加载器之外,其他类加载器都需要有自己的父加载器。
那么加载类字节码文件时会由哪个类加载器来加载呢?这实际上就是“家长委托机制”。
image.png 当类加载器需要加载一个类时,它首先判断该类是否已经被加载。如果已经加载,则直接返回。如果尚未加载,则会委托给父类加载器。父类加载器也会重复相同的操作,直到“启动类加载器”。确认类还没有被加载后,启动类加载器会首先在对应目录%JAVA_HOME%jre/lib/中搜索该类。如果找到,则加载它。如果没有找到,则到子类加载器中重复同样的操作,在相应的目录中查找该类。最后进入用户定义的类加载器。如果还没有找到,则会抛出ClassNotFoundException 并退出。
自定义类加载器
我们看一下java.lang.ClassLoader.class中的几个核心方法。
loadClass(String name, boolean resolve)也是双亲委托模式的代码实现。
该方法首先会调用findLoadedClass(String)来检查类是否已经加载;
然后递归调用父类加载器。如果父类加载器不为null,则调用父类加载器的loadClass()方法查找该类并加载。如果父类加载器为null,则表示加载了引导类。设备;
如果没有父类加载器可以找到该类,则调用自定义的findClass()方法来查找并加载该类;
最后根据resolve值选择是否动态链接Class。
image.pngdefineClass(String name, byte[] b, int off, int len)该方法主要将类的字节流转换为java.lang.Class实例对象。
String name 表示二进制形式的类名
byte[] b 表示该类的字节流。字节流可以来自Class文件、网络或其他渠道
int off 表示该类的字节流的起始偏移量
int len 表示findClass(String name)类的字节流大小。该方法用于查找类。在自定义类加载器时,我们一般需要重写该方法。通过它获取要加载的类的字节码,然后调用defineClass()方法生成Class对象。
因此,如果要定制类加载器,必须满足以下要求:
1.继承java.lang.ClassLoader.class类
2. 重写findClass()方法
过程让我们尝试编写一个测试类
公开课测试{
公共字符串演示1(){
返回“你好cseroad”;
}
}编译该类,生成类字节码文件。
然后写一个方法获取该类的字节码二进制文件的字节数组。
导入java.io.ByteArrayOutputStream;
导入java.io.File;
导入java.io.FileInputStream;
导入java.util.Arrays;
/**
* @作者cseroad
*/
公开课演示{
公共静态字节[] getBytesByFile(String pathStr) {
文件file=new File(pathStr);
尝试{
FileInputStream in=new FileInputStream(文件);
ByteArrayOutputStream baos=new ByteArrayOutputStream();
字节[] buf=新字节[1024];
int len=-1;
while ((len=in.read(buf)) !=-1) {
baos.write(buf, 0, len);
}
byte[] 数据=baos.toByteArray(); //读取字节码的二进制数据
附寄();
baos.close();
返回数据;
} catch (异常e) {
e.printStackTrace();
}
返回空值;
}
公共静态无效主(字符串[] args){
演示演示=new Demo();
byte[] bytesByFile=demo.getBytesByFile("/Users/cseroad/IdeaProjects/Project_Java/out/product/Java_study/com/atguigu/test/Test.class");
System.out.println(Arrays.toString(bytesByFile));
}
}要实现自定义类加载器,首先创建一个继承ClassLoader加载器的类并重写findClass()方法。
公共类TestClassLoad 扩展ClassLoader {
@覆盖
protected Class?findClass(String name) 抛出ClassNotFoundException {
返回super.findClass(name);
}
}在该方法中,调用defineClass()方法生成Class对象。
受保护的类?findClass(字符串名称){
字节[]字节=新字节[]{-54, -2, -70, -66, 0, 0, 0, 55, 0, 20, 10, 0, 4, 0, 16, 8, 0, 17, 7, 0, 18, 7, 0, 19, 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、18、76、 111, 99, 97, 108, 86, 97, 114, 105, 97, 98, 108, 101, 84, 97, 98, 108, 101, 1, 0, 4, 116, 104, 105, 115, 1, 0, 23, 76, 99, 111, 109, 47, 97, 116, 103, 117, 105, 103, 117, 47, 116, 101, 115, 116, 47, 84, 101, 115, 116, 59, 1, 0, 5, 68, 101, 109, 111, 49, 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、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、5、0、6、1、0、13、104、101、108、108、111、32、99、115、101、114、111、 97、100、1、0、21、99、111、109、47、97、116、103、117、105、103、117、47、116、101、115、116、47、84、101、115、 116, 1, 0, 16, 106, 97, 118, 97, 47, 108, 97, 110, 103, 47, 79, 98, 106, 101, 99, 116, 0, 33, 0, 3, 0, 4, 0, 0, 0, 0, 0, 2, 0, 1, 0, 5, 0, 6, 0, 1, 0, 7, 0, 0, 0, 47, 0, 1, 0, 1, 0, 0, 0, 5, 42, -73, 0, 1, -79, 0, 0, 0, 2, 0, 8, 0, 0, 0, 6, 0, 1, 0, 0, 0, 7, 0, 9, 0, 0, 0, 12, 0, 1, 0, 0, 0, 5, 0, 10, 0, 11, 0, 0, 0, 1, 0, 12, 0, 13, 0, 1, 0, 7, 0, 0, 0, 45, 0, 1, 0, 1, 0, 0, 0, 3, 18, 2, -80, 0, 0, 0, 2, 0, 8 , 0, 0, 0, 6, 0, 1, 0, 0, 0, 9, 0, 9, 0, 0, 0, 12, 0, 1, 0, 0, 0, 3, 0, 10, 0 , 11, 0, 0, 0, 1, 0, 14, 0, 0, 0, 2, 0, 15};
return super.defineClass("com.atguigu.test.Test",bytes, 0, bytes.length);
}main函数调用loadClass()方法自动加载类。
公共静态无效主(字符串[] args)抛出ClassNotFoundException {
TestClassLoad testClassLoad=new TestClassLoad();
//类?aClass=testClassLoad.findClass("com.atguigu.test.Test");
Class?aClass=testClassLoad.loadClass("com.atguigu.test.Test");
System.out.println(aClass);
然后使用反射创建对象并调用方法。
公共静态无效主(字符串[] args)抛出ClassNotFoundException,NoSuchMethodException,IllegalAccessException,InstantiationException,InitationTargetException {
TestClassLoad testClassLoad=new TestClassLoad();
//类?aClass=testClassLoad.findClass("com.atguigu.test.Test");
Class?aClass=testClassLoad.loadClass("com.atguigu.test.Test");
System.out.println(aClass);
对象o=aClass.newInstance();
方法demo1=aClass.getMethod("Demo1");
对象调用=demo1.invoke(o);
System.out.println(调用);
}总而言之,代码是:
导入java.lang.reflect.InitationTargetException;
导入java.lang.reflect.Method;
导入java.util.Base64;
公共类TestClassLoad 扩展ClassLoader {
@覆盖
受保护的类?findClass(字符串名称){
字节[]字节=新字节[]{-54, -2, -70, -66, 0, 0, 0, 55, 0, 20, 10, 0, 4, 0, 16, 8, 0, 17, 7, 0, 18, 7, 0, 19, 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、18、76、 111, 99, 97, 108, 86, 97, 114, 105, 97, 98, 108, 101, 84, 97, 98, 108, 101, 1, 0, 4, 116, 104, 105, 115, 1, 0, 23, 76, 99, 111, 109, 47, 97, 116, 103, 117, 105, 103, 117, 47, 116, 101, 115, 116, 47, 84, 101, 115, 116, 59, 1, 0, 5, 68, 101, 109, 111, 49, 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、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、5、0、6、1、0、13、104、101、108、108、111、32、99、115、101、114、111、 97、100、1、0、21、99、111、109、47、97、116、103、117、105、103、117、47、116、101、115、116、47、84、101、115、 116, 1, 0, 16, 106, 97, 118, 97, 47, 108, 97, 110, 103, 47, 79, 98, 106, 101, 99, 116, 0, 33, 0, 3, 0, 4, 0, 0, 0, 0, 0, 2, 0, 1, 0, 5, 0, 6, 0, 1, 0, 7, 0, 0, 0, 47, 0, 1, 0, 1, 0, 0, 0, 5, 42, -73, 0, 1, -79, 0, 0, 0, 2, 0, 8, 0, 0, 0, 6, 0, 1, 0, 0, 0, 7, 0, 9, 0, 0, 0, 12, 0, 1, 0, 0, 0, 5, 0, 10, 0, 11, 0, 0, 0, 1, 0, 12, 0, 13, 0, 1, 0, 7, 0, 0, 0, 45, 0, 1, 0, 1, 0, 0, 0, 3, 18, 2, -80, 0, 0, 0, 2, 0, 8 , 0, 0, 0, 6, 0, 1, 0, 0, 0, 9, 0, 9, 0, 0, 0, 12, 0, 1, 0, 0, 0, 3, 0, 10, 0 , 11, 0, 0, 0, 1, 0, 14, 0, 0, 0, 2, 0, 15};
//字符串classStr="yv66vgAAADcAFAoABAAQCAARBwASBwATAQAAGPGluaXQ+AQADKClWAQAEQ29kZQEAD0xpbmVOdW1iZXJUYWJsZQEAEkxvY2FsVmFyaWFibGVUYWJsZQEABHRoaXMBABdMY29tL2F0Z3VpZ3Uvd GV zdC9UZXN0OweEABURlbW8xAQAUKClMamF2YS9sYW5nL1N0cmluZzsBAApTb3VyY2VGaWxl AQAJVGVzdC5qYXZhDAAFAAYBAA1oZWxsbyBjc2Vyb2FkAQAVY29tL2F0Z3VpZ3UvdGVzdC9 UZX N0AQAQamF2YS9sYW5nL09iamVjdAAhAAMABAAAAAAAAgABAAUABgABAAcAAAAvAAEAAQAAAAUqtwABsQAAAAIACAAAAAAYAAQAAAAcACQAAA AwAAQAAAAUACgALAAAAAQAMAA0AAQAHAAAALQABAAEAAAADEgKwAAAAAAgAIAAAABgABAAAAACQAJAAAADAABAAAAAwAKAAAAAsAAAAAAAAA8=";
//byte[] bytes=Base64.getDecoder().decode(classStr);
return super.defineClass(bytes, 0, bytes.length);
}
公共静态无效主(字符串[] args)抛出ClassNotFoundException,NoSuchMethodException,IllegalAccessException,InstantiationException,InitationTargetException {
TestClassLoad testClassLoad=new TestClassLoad();
//类?aClass=testClassLoad.findClass("com.atguigu.test.Test");
Class?aClass=testClassLoad.loadClass("com.atguigu.test.Test");
System.out.println(aClass);
对象o=aClass.newInstance();
方法demo1=aClass.getMethod("Demo1");
对象调用=demo1.invoke(o);
System.out.println(调用);
}
}传入字节数组时,可以直接读取字节码文件转换为字节数组,也可以先base64编码然后解码读取。
image.png
URLClassLoader
URLClassLoader继承ClassLoader,可以通过java.net.URLClassLoader.class类加载器从本地或网络指定位置加载类。
public static void main(String[] args) 抛出NoSuchMethodException、IllegalAccessException、InstantiationException、InitationTargetException、MalformedURLException、ClassNotFoundException {
//从文件系统目录加载
文件f=new File("/Users/cseroad/Downloads/");
url url=f.toURL();
//从网络加载
//URL url=new URL("http://127.0.0.1:8000/");
URLClassLoader test1=new URLClassLoader(new URL[]{url});
Class?aClass=test1.loadClass("com.atguigu.test.Demo");
System.out.println(aClass);
对象o=aClass.newInstance();
方法demo1=aClass.getMethod("Demo2");
对象调用=demo1.invoke(o);
System.out.println(调用);
}image.png
扩展
上面我们学习了如何自定义类加载器来调用其方法,加载类字节码文件并生成对应的Class对象。基于此,您可以编写恶意类,并通过自定义类加载器生成恶意Class对象。
public void Eval() 抛出IOException {
Runtime.getRuntime().exec("/System/Applications/Calculator.app/Contents/MacOS/Calculator");
}创建Eval()方法来执行系统命令。
自定义类加载器加载字节码文件。
公共无效测试()抛出NoSuchMethodException,IllegalAccessException,InstantiationException,InitationTargetException,MalformedURLException,ClassNotFoundException {
TestClassLoad testClassLoad=new TestClassLoad();
字符串类Str="yv66vgAAADcAJAoABwAWCAAXCgAYABkIABoKABgAGwcAHAcAHQEABjxpbml0PgEAAygpVgEABENvZGUBAA9MaW5lTnVtYmVyVGFibGUBABJMb2NhbFZhcmlhYmxlVGFibGUBAAR0aGlzAQAXTGNvb S9h dGd1aWd1L3Rlc3QvVGVzdDsBAAVEZW1vMQEAFCgpTGphdmEvbGFuZy9TdHJpbmc7AQAERXZhbA EACKV4Y2VwdGlvbnMHAB4BAApTb3VyY2VGaWxlAQAJVGVzdC5qYXZhDAAIAAkBAA1oZWxsbyBj c2Vyb2FkBwAfDAAgACEBAD0vU3lzdGVtL0FwcGxpY2F0aW9ucy9DYWxjdWxhdG9yLmFwcC9Db250ZW50cy9NYWNPUy9DYWxjdWxhdG9yDAAiACMBABVjb20vYXRndWlndS90ZXN0L1Rlc 3QBABBqYXZhL2xhbmcvT2JqZWN0AQATAMF2YS9pby9JT0V4Y2VwdGlvbgEAEWphdmEvbGFuZy9 SDW50aW1LAQAKZ2V0UnVudGltZQEAFSgpTGphdmEvbGFuZy9SDW50aW1lOwEABGV4ZWMBAC COTG phdmEvbGFuZy9TdHJpbmc7KUxqYXZhL2xhbmcvUHJvY2VzczsAIQAGAAcAAAAAAAMAAQAIAAKAAQAAKAAAALwABAAEAAAAFKrcAAb EAAAACAAsAAAAGAAEAAAAJAAwAAAAMAAEAAAAFAA0ADgAAAAEADwAQAAEACgAAAC0AAQABAAAAAxICsAAAAAAACwA AAAYAAQAAAAs AADAAAAwAAQAAAAMADQAOAAAAAQARAAkAAgAKAAAAOAACAAEAAAAKuAADEgS2AAVXsQAAAAIACwAAAAoAAgAAAA4ACQAPAAwAAAAMAAEAAAAKAA0ADgAAABIAAAAEAAEAEwABABQAAAACABU=";
byte[] bytes=Base64.getDecoder().decode(classStr);
Class?aClass=testClassLoad.defineClass("com.atguigu.test.Test", bytes, 0, bytes.length);
System.out.println(aClass);
对象o=aClass.newInstance();
方法demo1=aClass.getMethod("Eval");
对象调用=demo1.invoke(o);
System.out.println(调用);
}image.png
总结
在不熟悉JVM的情况下,学习了JVM内置的三个类加载器,如何自定义类加载器实现方法调用,以及如何使用URLClassLoader远程加载类并调用方法执行代码。
好了,关于深入理解Java类加载机制:类加载器详解和的问题到这里结束啦,希望可以解决您的问题哈!
【深入理解Java类加载机制:类加载器详解】相关文章:
2.米颠拜石
3.王羲之临池学书
8.郑板桥轶事十则
用户评论
想了解下Java程序是怎么运行起来的,从类加载开始深入探究吧!
有12位网友表示赞同!
Java的类加载机制挺有意思,感觉是理解它底层运作的关键。
有5位网友表示赞同!
之前一直觉得Java代码执行很神奇,现在看来类加载器扮演着十分重要的角色.
有8位网友表示赞同!
学习JAVA的基礎知识,这个类的加载机制必不可少啊!
有13位网友表示赞同!
看这篇博文或许能让我更深刻地理解Java是如何实现动态加载等功能.
有20位网友表示赞同!
刚开始接触Java,感觉类加载器这块很深奥,希望这篇博客能给我一些启发。
有8位网友表示赞同!
在开发时遇到过类似问题,正好来学习下Java类的加载机制,避免未来麻烦。
有14位网友表示赞同!
JAVA开发的同学来说,这个学习内容应该比较关键啊!
有7位网友表示赞同!
之前只知道Java编译成字节码,现在才知道还有个类加载器把这些字节码变成程序可以调用的形式.
有17位网友表示赞同!
如果掌握了Java类的加载机制,可能就能写出更优化的代码吧?
有15位网友表示赞同!
想要深入Java这门语言,需要了解每一层细节,包括类加载器。
有9位网友表示赞同!
对编程有追求的同学一定要认真学习一下这个知识点!
有18位网友表示赞同!
这篇博文能让我更加理解Java底层机制运作。很期待!
有5位网友表示赞同!
Java类加载器一直是一个有点模糊的概念,希望通过文章可以更清晰地了解它。
有11位网友表示赞同!
在学习Java的过程中,总是会遇到一些难点,相信这篇文章能够解答我的疑问!
有8位网友表示赞同!
Java程序的运行机制真是复杂啊! 从这篇博文开始慢慢深入理解吧!
有18位网友表示赞同!
我觉得类加载器对Java开发者的实践有很大的帮助!
有7位网友表示赞同!
学习完Java类的加载机制之后,可以更好地调试和优化我们的代码。
有15位网友表示赞同!