欢迎来真孝善网,为您提供真孝善正能量书籍故事!

深入理解Java类加载机制:类加载器详解

时间:11-15 名人轶事 提交错误

类加载器简介

我们知道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程序是怎么运行起来的,从类加载开始深入探究吧!

    有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位网友表示赞同!

【深入理解Java类加载机制:类加载器详解】相关文章:

1.蛤蟆讨媳妇【哈尼族民间故事】

2.米颠拜石

3.王羲之临池学书

4.清代敢于创新的“浓墨宰相”——刘墉

5.“巧取豪夺”的由来--米芾逸事

6.荒唐洁癖 惜砚如身(米芾逸事)

7.拜石为兄--米芾逸事

8.郑板桥轶事十则

9.王献之被公主抢亲后的悲惨人生

10.史上真实张三丰:在棺材中竟神奇复活