Java 类加载机制

admin 2022年5月24日09:29:02评论25 views字数 3549阅读11分49秒阅读模式

Java 类加载机制

类加载过程即是指JVM虚拟机把.class文件中类信息加载进内存,并进行解析生成对应的class对象的过程。过程如上图。需要说明得是,JVM并不是一开始就把所有得类都加载进内存,二十遇到哪个类,发现内存中没有这个类得class信息,再去加载,且只加载一次。

下面就具体说下类加载的每个过程。

一、java.lang.ClassLoader



java.lang.ClassLoader 类的基本职责就是根据一个指定的类的名称,找到或者生成其对应的字节代码,然后从这些字节代码中定义出一个 Java 类,即 java.lang.Class 类的一个实例。除此之外, ClassLoader 还负责加载 Java 应用所需的资源,如图像文件和配置文件等。

Java 类加载机制


二、加载



通过类的全限定名(包名 + 类名),把class字节码文件从各个来源通过类加载器装载入内存中。加载二进制数据到内存 —> 映射成jvm能识别的结构 —> 在内存中生成class文件。

类加载器按照类的来源不同分为三种:启动类加载器,扩展类加载器,应用类加载器,以及用户的自定义类加载器(主要是用来防止反编译)。

1.启动类/引导类:Bootstrap ClassLoader

这个类加载器使用C/C++语言实现的,嵌套在JVM内部,java程序无法直接操作这个类。是用原生代码来实现的,并不继承自 java.lang.ClassLoader 。它用来加载Java核心类库,如:JAVA_HOME/jre/lib/rt.jar、resources.jar、sun.boot.class.path路径下的包,用于提供jvm运行所需的包。

并不是继承自java.lang.ClassLoader,它没有父类加载器

它加载扩展类加载器和应用程序类加载器,并成为他们的父类加载器

出于安全考虑,启动类只加载包名为:java、javax、sun开头的类

2.扩展类加载器:Extension ClassLoader

位于 $JAVA_HOME/jre/lib/ext 目录下。开发者也可以把自己编写的类打包成 jar 文件放入该目录下;

Java语言编写,由sun.misc.Launcher$ExtClassLoader实现,我们可以用Java程序操作这个加载器派生继承自java.lang.ClassLoader,父类加载器为启动类加载器

3.应用程序类加载器:Application Classloader

开发者在项目中编写的类,这些文件将由 AppClassLoader 加载器进行加载

Java语言编写,由sun.misc.Launcher$AppClassLoader实现。派生继承自java.lang.ClassLoader,父类加载器为启动类加载器

4.自定义类加载器

远程加载如(本地文件/网络下载)的方式,则必须要自己自定义一个 ClassLoader,复写其中的 findClass() 方法才能得以实现。如果没有太复杂的需求,可以直接继承URLClassLoader类,重写loadClass方法,具体可参考AppClassLoader和ExtClassLoader。开发人员可以通过继承java.lang.ClassLoader 类的方式实现自己的类加载器,重写 findClass()方法,以满足一些特殊的需求。


三、链接



1.验证

为了保证加载进来的字节流符合虚拟机规范,不会造成安全错误。

包括对于文件格式的验证,比如常量中是否有不被支持的常量?文件中是否有不规范的或者附加的其他信息?

对于元数据的验证,比如该类是否继承了被final修饰的类?类中的字段,方法是否与父类冲突?是否出现了不合理的重载?

对于字节码的验证,保证程序语义的合理性,比如要保证类型转换的合理性。

对于符号引用的验证,比如校验符号引用中通过全限定名是否能够找到对应的类?校验符号引用中的访问性(private,public等)是否可被当前类访问?

2.准备

为类变量(注意,不是实例变量)分配内存,并且赋予初值。

3.解析

解析阶段,虚拟机会把所有的类名,方法名,字段名这些符号引用替换为具体的内存地址或偏移量,也就是直接引用。


四、初始化


关键词:static

这个阶段主要是对类变量初始化,是执行类构造器的过程。

换句话说,只对static修饰的变量或语句进行初始化。

如果初始化一个类的时候,其父类尚未初始化,则优先初始化其父类。

如果同时包含多个静态变量和静态代码块,则按照自上而下的顺序依次执行。


五、双亲委派


1.简介

Java 类加载机制

(1)如果一个类加载器接收到了类加载的请求,它自己不会先去加载,会把这个请求委托给父类加载器去执行。

(2)如果父类还存在父类加载器,则继续向上委托,一直委托到启动类加载器:Bootstrap ClassLoader

(3)如果父类加载器可以完成加载任务,就返回成功结果,如果父类加载失败,就由子类自己去尝试加载,如果子类加载失败就会抛出ClassNotFoundException异常,这就是双亲委派模式

2.第三方包加载方式:反向委派机制

在Java应用中存在着很多服务提供者接口(Service Provider Interface,SPI),这些接口允许第三方为它们提供实现,如常见的 SPI 有 JDBC、JNDI等,这些 SPI 的接口属于 Java 核心库,一般存在rt.jar包中,由Bootstrap类加载器加载。而Bootstrap类加载器无法直接加载SPI的实现类,同时由于双亲委派模式的存在,Bootstrap类加载器也无法反向委托AppClassLoader加载器SPI的实现类。在这种情况下,我们就需要一种特殊的类加载器来加载第三方的类库,而线程上下文类加载器(双亲委派模型的破坏者)就是很好的选择。

从图可知rt.jar核心包是有Bootstrap类加载器加载的,其内包含SPI核心接口类,由于SPI中的类经常需要调用外部实现类的方法,而jdbc.jar包含外部实现类(jdbc.jar存在于classpath路径)无法通过Bootstrap类加载器加载,因此只能委派线程上下文类加载器把jdbc.jar中的实现类加载到内存以便SPI相关类使用。显然这种线程上下文类加载器的加载方式破坏了“双亲委派模型”,它在执行过程中抛弃双亲委派加载链模式,使程序可以逆向使用类加载器,当然这也使得Java类加载器变得更加灵活。

Java 类加载机制

3.沙箱安全机制(为什么不能自定义String类)

自定义 String 类,但是在加载自定义 String 类的时候会率先使用引导类加载器加载,而引导类加载器在加载的过程中会先加载 JDK 自带的文件(rt.jar 包中的 javalangString.class),报错信息说没有 main 方法就是因为加载的 rt.jar 包中的 String 类。这样可以保证对 Java 核心源代码的保护,这就是沙箱安全机制。


六、Java运行时进行类的加载

(写漏洞利用中回显等需求时用得到)



loadClass()、forName() 和 defineClass()。

ClassLoader.loadClass()与Class.forName()是反射用来构造类,给一个类名即可.返回值是Class,注意一点,forName(),会执行目标class的static代码块方法

loadClass 应该在URLClassLoader里面用的多,这个涉及到动态加载jar

解决工具中使用到同一个类,加载不同的jar包版本问题

关于:Java RCE中类的反射获取&动态加载 参考:https://mp.weixin.qq.com/s/fkd1Qhgr4BcQXaguCd5TAg

安全开发,漏洞利用时还会经常遇到一个需求:获取当前ClassLoader

// 方式一:获取当前类的 ClassLoaderclazz.getClassLoader()// 方式二:获取当前线程上下文的 ClassLoaderThread.currentThread().getContextClassLoader()// 方式三:获取系统的 ClassLoaderClassLoader.getSystemClassLoader()// 方式四:获取调用者的 ClassLoaderDriverManager.getCallerClassLoader()


六、参考链接


https://segmentfault.com/a/1190000037574626

https://xiaozhuanlan.com/topic/8274065913

https://mp.weixin.qq.com/s/fkd1Qhgr4BcQXaguCd5TAg



我也讨厌我喜怒无常的样子,真的很努力的在劝自己放弃


原文始发于微信公众号(赛博少女):Java 类加载机制

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2022年5月24日09:29:02
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   Java 类加载机制http://cn-sec.com/archives/880009.html

发表评论

匿名网友 填写信息