当前位置: 首页 > article >正文

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/";
    //自定义的类加载器,加载来自网络的字节码
    @Override
    protectedClass<?> 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/


http://www.kler.cn/a/445525.html

相关文章:

  • lua和C API库一些记录
  • SpringBoot + vue 管理系统
  • HarmonyOS NEXT 实战之元服务:静态案例效果---我的热门应用服务
  • 【SpringBoot教程】IDEA快速搭建正确的SpringBoot版本和Java版本的项目
  • Excel 面试 01 “Highlight in red the 10 lowest orders”
  • 【Java】IO流练习
  • 【操作系统】每日 3 题(七十)
  • 数据结构——常见数据结构和应用
  • 项目搭建+图片(添加+图片)
  • dolphinscheduler服务RPC框架源码解析(八)RPC提供者服务整合Spring框架实现
  • React-antd组件库 - 让Menu子菜单项水平排列 - 下拉菜单排序 - 自定义子菜单展示方式
  • 电商后台革命:RPA 自动化商品信息录入与更新【52rpa.com】
  • MongoDB常见面试题总结(上)
  • 用链表的详解和综合应用——实现链表的创建、输出、删除及添加结点(C语言)
  • VR线上展厅的色彩管理如何影响用户情绪?
  • 【踩坑】Pytorch与CUDA版本的关系及安装
  • 基于Spring Boot的房屋租赁管理系统
  • 在 Ubuntu 上安装 Muduo 网络库的详细指南
  • 巧记斜边函数hypot
  • JavaScript网络请求( XMLHttpRequest 对象,进度事件, 跨源资源共享)
  • express+mysql实现注册功能
  • NPM国内镜像源多选择与镜像快速切换工具(nrm)介绍
  • 慢牛提速经典K线形态-突破下跌起始位和回档三五线,以及徐徐上升三种形态
  • 软件工程
  • iPhone通话记录生成器,苹果一键生成通话记录,手机ios制造专家
  • 差分矩阵(Difference Matrix)与累计和矩阵(Running Sum Matrix)的概念与应用:中英双语