欢迎来到尧图网

客户服务 关于我们

您的位置:首页 > 新闻 > 国际 > JAVA安全之类加载器

JAVA安全之类加载器

2024/12/22 19:53:43 来源:https://blog.csdn.net/Liyu_6618/article/details/144538420  浏览:    关键词:JAVA安全之类加载器

作者介绍

图片

类加载器的介绍

我们写的代码都是.java结尾,然后通过javac编译成.class的字节码文件,这个字节码文件存放在本地

.class字节码保存着转换后的虚拟机指令,使用到对象的时候,会吧这个.class字节码就要加载到java虚拟机内存里面,这个过程就叫做类加载

在开发过程中,了解类加载是很有作用的,ClassLoader会把我们的class文件加载到jvm虚拟机里面,加载到jvm虚拟机里面就可以运行了,他不会吧把全部的class的全部东西加载到jvm虚拟机里面,他会去动态加载调用,想想也是的,一次性加载那么多jar包那么多class那内存不崩溃。本文的目的也是学习ClassLoader这种加载机制

java虚拟机有两个分区:

  • • 方法区:这个区存放着字节码的二进制数据

  • • 堆区:这个区会生成class对象,去引用方法区的节码的二进制数据

java内的自带的三个加载器

  1. 1. Bootstrap ClassLoader(引导类加载器)负责加载java 核心类库,例如 java.lang.xxx包中的类加载的文件夹xxx/lib,这个ClassLoader完全是jvm自己控制的,需要加载哪个类是最顶层的加载器,使用本地代码实现C和C++,没有直接的 Java 对象表示

  2. 2. Extension ClassLoader(扩展类加载器)加载扩展类库,通常是放置在 xxx/lib/ext 目录或通过系统属性由 Java 代码实现,是 ClassLoader 的子类

  3. 3. Application ClassLoader(应用类加载器)面向我们用户的加载器,负责加载当前应用 classpath 下的所有 jar 包和类

Bootstrap ClassLoader(引导类加载器)查看加载了什么

可以使用sun.boot.class.path系统属性查看加载了什么东西,sun.boot.class.path 是一个系统属性,用于表示 Bootstrap ClassLoader(引导类加载器) 的类加载路径

代码

package 类加载.java内的自带的三个加载器;publicclassBootstrapClassLoader引导类加载器{publicstaticvoidmain(String[] args){// 获取 Bootstrap ClassLoader 的加载路径StringbootClassPath=System.getProperty("sun.boot.class.path");System.out.println("Bootstrap ClassLoader加载的路径:\n"+ bootClassPath);
}
}

运行结果

Bootstrap ClassLoader加载的路径:
/home/zss/YingYong/jdk1.8.0_65/jre/lib/resources.jar:/home/zss/YingYong/jdk1.8.0_65/jre/lib/rt.jar:/home/zss/YingYong/jdk1.8.0_65/jre/lib/sunrsasign.jar:/home/zss/YingYong/jdk1.8.0_65/jre/lib/jsse.jar:/home/zss/YingYong/jdk1.8.0_65/jre/lib/jce.jar:/home/zss/YingYong/jdk1.8.0_65/jre/lib/charsets.jar:/home/zss/YingYong/jdk1.8.0_65/jre/lib/jfr.jar:/home/zss/YingYong/jdk1.8.0_65/jre/classes

Extension ClassLoader(扩展类加载器)查看加载了什么

java.ext.dirs 系统属性查看

package 类加载.java内的自带的三个加载器;publicclassExtensionClassLoader扩展类加载器{publicstaticvoidmain(String[] args){System.out.println(System.getProperty("java.ext.dirs"));}
}

运行结果

/home/zss/YingYong/jdk1.8.0_65/jre/lib/ext:/usr/java/packages/lib/ext

Application ClassLoader(应用类加载器)查看加载了什么

java.ext.dirs 系统属性查看

package 类加载.java内的自带的三个加载器;publicclassApplicationClassLoader应用类加载器{publicstaticvoidmain(String[] args){// 获取 AppClassLoader 的类路径System.out.println(System.getProperty("java.class.path"));}
}

运行结果

/home/zss/YingYong/jdk1.8.0_65/jre/lib/charsets.jar:/home/zss/YingYong/jdk1.8.0_65/jre/lib/deploy.jar:/home/zss/YingYong/jdk1.8.0_65/jre/lib/ext/cldrdata.jar:/home/zss/YingYong/jdk1.8.0_65/jre/lib/ext/dnsns.jar:/home/zss/YingYong/jdk1.8.0_65/jre/lib/ext/jaccess.jar:/home/zss/YingYong/jdk1.8.0_65/jre/lib/ext/jfxrt.jar:/home/zss/YingYong/jdk1.8.0_65/jre/lib/ext/localedata.jar:/home/zss/YingYong/jdk1.8.0_65/jre/lib/ext/nashorn.jar:/home/zss/YingYong/jdk1.8.0_65/jre/lib/ext/sunec.jar:/home/zss/YingYong/jdk1.8.0_65/jre/lib/ext/sunjce_provider.jar:/home/zss/YingYong/jdk1.8.0_65/jre/lib/ext/sunpkcs11.jar:/home/zss/YingYong/jdk1.8.0_65/jre/lib/ext/zipfs.jar:/home/zss/YingYong/jdk1.8.0_65/jre/lib/javaws.jar:/home/zss/YingYong/jdk1.8.0_65/jre/lib/jce.jar:/home/zss/YingYong/jdk1.8.0_65/jre/lib/jfr.jar:/home/zss/YingYong/jdk1.8.0_65/jre/lib/jfxswt.jar:/home/zss/YingYong/jdk1.8.0_65/jre/lib/jsse.jar:/home/zss/YingYong/jdk1.8.0_65/jre/lib/management-agent.jar:/home/zss/YingYong/jdk1.8.0_65/jre/lib/plugin.jar:/home/zss/YingYong/jdk1.8.0_65/jre/lib/resources.jar:/home/zss/YingYong/jdk1.8.0_65/jre/lib/rt.jar:/home/zss/笔记/代码/java/javaAll3/target/classes:/home/zss/YingYong/idea-IU-241.18034.62/lib/idea_rt.jar

加载器加载顺序

  1. 1. 检查类是否已加载 每个类加载器在加载类之前,会先检查目标类是否已经被加载。如果已加载,则直接返回,不重复加载

  2. 2. 双亲委派机制(下面详细说) 当某个类加载器收到加载类的请求时,按照以下顺序进行处理:

    1. 1. 委托父加载器加载 首先将加载请求委托给父加载器(即上一层的加载器)

    2. 2. 父加载器加载失败时,尝试自身加载 如果父加载器无法加载目标类,则当前加载器会尝试自行加载

我们先了解一下getParent()方法

每个 ClassLoader 都有一个父加载器,通过调用 getParent() 方法,我们可以获取到该类加载器的父加载器

代码演示加载

创建一个类abc的java类

package 类加载.java加载器加载顺序;public class abc {
}

我们加载这个abc这个类看看他的加载器

package 类加载.java加载器加载顺序;publicclassrun{publicstaticvoidmain(String[] args){// 获取 abc 类的类加载器(应用类加载器)ClassLoaderappClassLoader= abc.class.getClassLoader();System.out.println(appClassLoader);// 输出 abc 类的类加载器// 获取 abc 类的父加载器System.out.println(appClassLoader.getParent());// 输出父加载器(扩展类加载器)// 获取父加载器的父加载器System.out.println(appClassLoader.getParent().getParent());// 输出引导类加载器(为 null)}
}

运行结果

sun.misc.Launcher$AppClassLoader@14dad5dc
sun.misc.Launcher$ExtClassLoader@1540e19d
null

为什么没有显示Bootstrap ClassLoader因为由于引导类加载器(Bootstrap ClassLoader)是 JVM 内部实现的,他是c++编写的

双亲委派

ClassLoader 类使用委托模型来搜索类和资源,每个 ClassLoader 实例都有一个相关的父类加载器。需要查找类或资源时,ClassLoader 实例会在试图亲自查找类或资源之前,将搜索类或资源的任务委托给其父类加载器,原理详情下面的loadClass分析代码执行过程就是双亲委派机制

ClassLoader 类的方法

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

方法名作用备注
loadClass(String name)加载指定名称的类,遵循双亲委派模型。常用方法,默认实现先调用 findClass
findClass(String name)寻找类的定义,通常由自定义加载器重写。默认抛出 ClassNotFoundException
defineClass(String name, byte[] b, int off, int len)将二进制字节数组转换为 Java 类对象。用于自定义类加载器加载字节码。
resolveClass(Class<?> c)解析类及其依赖关系。通常在加载类后手动调用以完成解析。
findLoadedClass(String name)检查类是否已被加载,如果已加载则返回 Class 对象。防止重复加载同一个类。
getParent()获取当前类加载器的父加载器。返回父类加载器;引导类加载器返回 null
getSystemClassLoader()获取系统类加载器(即 AppClassLoader)。用于加载应用程序类路径下的类。
getResource(String name)查找指定名称的资源,返回资源的 URL。可用于加载配置文件等资源。
getResourceAsStream(String name)以输入流形式查找资源,便于读取资源内容。配合流操作读取文件资源。
getResources(String name)查找所有与指定名称匹配的资源,返回一个枚举的 URL 集合。用于加载多个相同名称的资源。
clearAssertionStatus()清除当前类加载器及其加载的类的断言状态。重置断言状态。
setDefaultAssertionStatus(boolean enabled)设置默认断言状态,适用于当前类加载器加载的所有类。改变全局默认断言行为。
setClassAssertionStatus(String className, boolean enabled)设置特定类的断言状态。单独调整某个类的断言启用状态。
setPackageAssertionStatus(String packageName, boolean enabled)设置特定包的断言状态。对某个包中的所有类调整断言启用状态。

loadClass对象方法介绍

loadClass 是 ClassLoader 类中最核心的方法的其中一个,用于加载指定名称的类。它遵循 双亲委派模型

点进去

图片

在点进去

图片

里面的源代码是如下:

protected Class<?> loadClass(String name,boolean resolve)throwsClassNotFoundException{synchronized(getClassLoadingLock(name)){// 这段代码的作用是保证类加载过程的线程安全性Class<?> c = findLoadedClass(name);if(c ==null){longt0=System.nanoTime();//记录当前时间的纳秒级时间戳try{if(parent !=null){// parent是当前类加载器的父类加载器c = parent.loadClass(name,false);}else{c = findBootstrapClassOrNull(name);}}catch(ClassNotFoundException e){}if(c ==null){longt1=System.nanoTime();//记录当前时间的纳秒级时间戳c = findClass(name);sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);// 计算时间sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);// 计算时间sun.misc.PerfCounter.getFindClasses().increment();// 计算时间}}if(resolve){resolveClass(c);}return c;}
}

读一下源代码

1、第一行定义的这个loadClass,他接收两个参数

protected Class<?> loadClass(String name, boolean resolve)throws ClassNotFoundException{

第一个参数是要加载的类的完全限定名称,第二个参数如果设置是true则调用 resolveClass(Class<?> c)

2、第三行,

Class<?> c = findLoadedClass(name); 

用于检查某个类是否已经被当前类加载器加载过,如果已经加载直接返回该类的 Class对象,如果未加载返回 null

3、第八行

parent.loadClass(name, false);

这个会拿他的父类去加载一遍这个类,看看有没有这个类,这个调用是基于父类委派模型,如果父加载器存在它会尝试加载该类

4、第十行

c = findBootstrapClassOrNull(name);

如果没有父类(parent)等于空就会调用上面的这行代码,findBootstrapClassOrNull是会去尝试加载类

5、第15行

if (c == null) {

如果类 c 还没有被加载

6、第17行

c = findClass(name);

用于加载指定名称的类,如果当前加载器无法加载该类可能会抛出ClassNotFoundException

findClass对象方法介绍

findClass(String name)

参数是一个类名

findClass方法用于在类加载器中查找并加载指定名称的类,他会根据类的名称查询加载类的字节码返回一个 Class对象,如果没有找到抛出ClassNotFoundException异常

defineClass对象方法介绍

defineClass(String name, byte[] b, int off, int len)

name:要定义的类的全限定名(包括包名)例如com.xxx.aaabbb

b:包含类字节码的字节数组,这个字节数组应该包含类的所有字节码(从 .class 文件读取出来的内容)

off:字节数组的起始偏移量,通常为 0,表示从字节数组的第一个字节开始读取

len:要读取的字节长度。如果传入的字节数组 b 是整个类的字节码,通常传入该字节数组的长度

defineClass方法是将原始字节码(二进制)转换成 class对象的核心方法,比如.class文件、网络

它允许将已经加载的类字节码转化为可以在jvm 中使用的class实例,如果字节码不符合 jvm 的格式规范会抛出异常

方法使用ClassLoader

loadClass使用

创建一个MyClass方法下面是代码

package 类加载.类加载.test_loadClass;publicclassMyClass{privateStringmyField="Hello, World!";publicStringgetMyField(){return myField;}publicStringStudent(String name){System.out.println("Student: "+ name);return name;}
}

使用loadClass调用上面创建的类

package 类加载.类加载.test_loadClass;import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;publicclasstest_loadClass{publicstaticvoidmain(String[] args)throwsClassNotFoundException,NoSuchFieldException,NoSuchMethodException,InvocationTargetException,InstantiationException,IllegalAccessException{// 获取当前类加载器ClassLoaderclassLoader= test_loadClass.class.getClassLoader();// 使用 loadClass 方法加载类Class<?> clazz = classLoader.loadClass("类加载.类加载.test_loadClass.MyClass");System.out.println("Loaded class: "+ clazz.getName());// 方法返回某个类的所有public方法Method[] a = clazz.getMethods();for(Method i:a){System.out.println(i);}}
}

运行结果

Loaded class:类加载.类加载.test_loadClass.MyClass
public java.lang.String类加载.类加载.test_loadClass.MyClass.getMyField()
public java.lang.String类加载.类加载.test_loadClass.MyClass.Student(java.lang.String)
publicfinalvoid java.lang.Object.wait(long,int)throws java.lang.InterruptedException
publicfinalnativevoid java.lang.Object.wait(long)throws java.lang.InterruptedException
publicfinalvoid java.lang.Object.wait()throws java.lang.InterruptedException
publicboolean java.lang.Object.equals(java.lang.Object)
public java.lang.String java.lang.Object.toString()
publicnativeint java.lang.Object.hashCode()
publicfinalnative java.lang.Class java.lang.Object.getClass()
publicfinalnativevoid java.lang.Object.notify()
publicfinalnativevoid java.lang.Object.notifyAll()

Class.forName

Class.forName()也可以对类进行加载,但是Class.forName()和ClassLoader是有区别的

抽象类ClassLoader中实现的方法loadClass,loadClass只是加载,不会解析更不会初始化所反射的类,常用于做懒加载,提高加载速度,使用的时候再通过.newInstance()真正去初始化类

看一下Class.forName()加载一个累

创建一个被加载的类

package 类加载.forName和ClassLoader;public class MyClass {static {System.out.println("aaaaa");}}

调用MyClass代码

package 类加载.forName和ClassLoader;publicclasstest{publicstaticvoidmain(String[] args)throwsClassNotFoundException{// 动态加载 MyClass 类Class<?> clazz =Class.forName("类加载.forName和ClassLoader.MyClass");}
}

运行结果

aaaaa

自定义类加载

为什么要自定义类加载器,ClassLoader在默认情况下只会加载文件系统或 jar 包中加载类,这个限制很大,我们就可以自定义类加载器去加载网络或其他自定义位置类,可以类文件经过加密处理,然后接收然后解密后加载,增加应用安全性

自定义类加载的步骤

  1. 1. 继承 ClassLoader 类 自定义类加载器需要继承 ClassLoader 并重写其方法,如 findClass

  2. 2. 重写 findClass 方法 findClass 是类加载器的核心方法,用于加载类的字节码并将其定义为一个 Class 对象

  3. 3. 通过 defineClass 方法定义类 使用 defineClass 将字节码数组转换为 Class 对象

代码演示:

1、创建了一个类加载器

package 类加载.自定义类加载;import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URL;publicclassMyClassLoaderextendsClassLoader{staticString URL="http://127.0.0.1:8081/";//自定义的类加载器,加载来自网络的字节码@OverrideprotectedClass<?> findClass(String name)throwsClassNotFoundException{byte[] classData =null;try{classData = loadClassDataFromNetwork(name);}catch(IOException e){thrownewRuntimeException(e);}if(classData ==null){thrownewClassNotFoundException(name);}return defineClass(name, classData,0, classData.length);// 定义类}//模拟从网络加载类的字节码数据privatebyte[] loadClassDataFromNetwork(String className)throwsIOException{StringencodedClassName= className.replace('.','/')+".class";encodedClassName=java.net.URLEncoder.encode(encodedClassName,"UTF-8").replace("%2F","/");URLurl=newURL(URL + encodedClassName);//请求从远程服务器加载字节码InputStreaminputStream= url.openStream();ByteArrayOutputStreambyteArrayOutputStream=newByteArrayOutputStream();int byteRead;while((byteRead = inputStream.read())!=-1){byteArrayOutputStream.write(byteRead);}return byteArrayOutputStream.toByteArray();}
}

2、创建一个测试的类

名字就叫abc

package 类加载.自定义类加载;publicclassabc{
privateString message;publicabc(){this.message ="aaaaaaaaaaaaa";}publicabc(String var1){this.message = var1;}publicStringgetMessage(){returnthis.message;}publicvoidsetMessage(String var1){this.message = var1;}publicvoidprintMessage(){System.out.println(this.message);}
}

生成class文件

javac -d . abc.java

3、然后在创建一个测试我们创建的自定义类

TestMyClassLoader.java文件内容

package 类加载.自定义类加载;import java.io.UnsupportedEncodingException;
import java.lang.reflect.InvocationTargetException;publicclassTestMyClassLoader{
publicstaticvoidmain(String[] args)throwsClassNotFoundException,NoSuchMethodException,InvocationTargetException,InstantiationException,IllegalAccessException,UnsupportedEncodingException{MyClassLoadermyClassLoader=newMyClassLoader();// 创建自定义类加载器实例Class<?> loadedClass = myClassLoader.loadClass("类加载.自定义类加载.abc");//使用自定义类加载器加载类System.out.println("加载器信息:"+ loadedClass.getClassLoader());//打印类加载器信息// 实例化对象Objectobj= loadedClass.getDeclaredConstructor().newInstance();// 调用 printMessage 方法loadedClass.getMethod("printMessage").invoke(obj);}
}

整个命令结构:

图片

我们在这个目录下启动web服务

python3 -m http.server 8081

然后我们运行TestMyClassLoader.java可以看见被加载了

图片

参考

https://blog.csdn.net/succing/article/details/123308677

https://blog.csdn.net/succing/article/details/123308677

https://blog.csdn.net/briblue/article/details/54973413

书籍:《java代码审计》

https://gityuan.com/2016/01/24/java-classloader/

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com