深入解析 Java 类加载机制:原理与实践

更新:11-16 民间故事 我要投稿 纠错 投诉

大家好,关于深入解析 Java 类加载机制:原理与实践很多朋友都还不太明白,不过没关系,因为今天小编就来为大家分享关于的知识点,相信应该可以解决大家的一些困惑和问题,如果碰巧可以解决您的问题,还望关注下本站哦,希望对各位有所帮助!

类加载器简介

我们知道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();

字符串classStr="yv66vgAAADcAJAoABwAWCAAXCgAYABkIABoKABgAGwcAHAcAHQEABjxpbml0PgEAAygpVgEABENvZGUBAA9MaW5lTnVtYmVyVGFibGUBABJMb2NhbFZh cmlhYmxlVGFibGUBAAR0aGlzAQAXTGNvb S9hdGd1aWd1L3Rlc3QvVGVzdDsBAAVEZW1vMQEAFCgpTGphdmEvbGFuZy9TdHJpbmc7AQAERXZhbAEACKV4Y 2VwdGlvbnMHAB4BAApTb3VyY2VGaWxlAQAJVGVzdC5qYXZhDAAIAAkBAA1oZWxsbyBjc 2Vyb2FkBwAfDAAgACEBAD0vU3lzdGVtL0FwcGxpY2F0aW9ucy 9DYWxjdWxhdG9yLmFwcC9Db250ZW50cy9NYWNPUy9DYWxjdWxhdG9yDAAiACMBABVjb20vYXRndWlndS90ZXN0L1Rlc3 QBABBqYXZhL2xhbmcvT2JqZWN0 AQATAMF2YS9pby9JT0V4Y2VwdGlvbgEAEWphdmEvbGFuZy9SDW50aW1lAQAKZ2V0UnVudGltZQEAFSgpTGphdmEvbGFuZy9SdW50aW1lOWEBGV4ZWMBA Cco TGphdmEvbGFuZy9TdHJpbmc7KUxqYXZhL2xhbmcvUHJvY2VzczsAIQAGAAcAAAAAAAMAAQAIAAKAAQAAKAAAALwABAAEAAAAFKrcAAbEAAAACAAsAAAA GAAEAAAAJAAwAAAAMAAEAAAAFAA0ADgAAAAEADwAQAAEACgAAAC0AAQABAAAAAxICsAAAAAAIAACwAAAAYAA QAAAAsADAAAAAwAAQAAAAMADQAOAAAAAAQAR AAkAAgAKAAAAOAACAAEAAAAKuAADEgS2AAVXsQAAAAIACwAAAAoAAgAAAA4ACQAPAAwAAAAMAAEAAAAKAA0ADgAAABIAAAAEAAEEwABABQAAAACABU=";

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(调用);

关于本次深入解析 Java 类加载机制:原理与实践和的问题分享到这里就结束了,如果解决了您的问题,我们非常高兴。

用户评论

孤单*无名指

终于开始学习 Java 的类加载器了!貌似这个东西很基础也很重要。

    有19位网友表示赞同!

南初

一直没太懂 Java 里是怎么加载类的,看来要好好研究一下这篇文章才行。

    有11位网友表示赞同!

■孤独像过不去的桥≈

希望这篇文章能详细解释一下 Bootstrap、Extension 和 User 类加载器的区别。我很想搞明白它们的工作机制。

    有18位网友表示赞同!

浮殇年华

类加载器这个概念听起来有点抽象,需要多看几次才能理解清楚。

    有13位网友表示赞同!

敬情

感觉 Java 的体系结构设计的真是太棒了,类加载器这个机制让人叹为观止!

    有13位网友表示赞同!

服从

学习完这篇文章应该能更好地理解 Java 程序的运行原理吧?期待收获!

    有14位网友表示赞同!

失心疯i

之前写代码的时候并没有意识到类加载器的存在,现在想深入了解一下它的运作方式。

    有9位网友表示赞同!

怅惘

Java 类加载器这个话题真是太烧脑了!希望能学到一些实用的知识。

    有5位网友表示赞同!

哭着哭着就萌了°

这次学习 Java 的类加载器,希望可以解决我之前遇到的ClassNotFoundException问题。

    有17位网友表示赞同!

素婉纤尘

对于想要成为 Java 开发者的朋友来说,学习类加载器绝对是必不可少的知识!

    有9位网友表示赞同!

有阳光还感觉冷

这篇文章一定能让我对 Java 类加载器的概念有更深层次的理解。

    有17位网友表示赞同!

信仰

希望这篇文章能用通俗易懂的语言来讲解 Java 类加载器的工作原理,方便我理解。

    有16位网友表示赞同!

此刻不是了i

学习完这篇文章之后,应该可以写出更加高效、健壮的 Java 程序吧!

    有6位网友表示赞同!

来瓶年的冰泉

Java 的代码设计真是太精致了,类加载器的机制让我心神荡漾!

    有14位网友表示赞同!

人心叵测i

期待这篇文章能给我带来一些新的思考和启发。

    有20位网友表示赞同!

有恃无恐

学习新东西总是充满挑战,这次的 Java 类加载器学习之旅加油!

    有14位网友表示赞同!

眉黛如画

对于想深入了解 Java 底层的同学来说,Java 类加载器的知识非常重要。

    有9位网友表示赞同!

焚心劫

想要成为优秀的 Java 开发者,就必须熟练掌握类加载器的相关知识点。

    有11位网友表示赞同!

情如薄纱

这篇文章能帮助我更好地理解 Java 中类的生命周期!

    有10位网友表示赞同!

【深入解析 Java 类加载机制:原理与实践】相关文章:

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

2.米颠拜石

3.王羲之临池学书

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

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

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

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

8.郑板桥轶事十则

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

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

上一篇:深度挖掘:精选内容的摘录与解析 下一篇:基于贝叶斯理论的简单分类器:朴素贝叶斯分类器介绍