ClassLoader类加载器 & Java类加载机制 & 破坏双亲委托机制
ClassLoader类加载器
Java 中的类加载器大致可以分成两类:
一类是系统提供的:
- 引导类加载器(Bootstrap classloader):它用来加载 Java 的核心库(如rt.jar),是用原生代码而不是java来实现的,并不继承自java.lang.ClassLoader,除此之外基本上所有的类加载器都是java.lang.ClassLoader类的一个实例。
- 扩展类加载器(Extension classloader):它用来加载 Java 的扩展库。Java 虚拟机的实现会提供一个扩展库目录(一般为%JRE_HOME%/lib/ext)。该类加载器在此目录里面查找并加载 Java 类。
- 系统类加载器(System classloader或 App classloader):它根据当前Java 应用的类路径(CLASSPATH)来加载 Java 类。一般来说,Java 应用的类都是由它来完成加载的。可以通过 ClassLoader.getSystemClassLoader() 来获取它。
另外一类则是由 Java 应用开发人员编写的:
开发人员可以通过继承java.lang.ClassLoader 类的方式实现自己的类加载器,重写 findClass()方法,以满足一些特殊的需求。
当一个类加载和初始化的时候,类仅在有需要加载的时候被加载,类加载都是在运行期间执行的;
比如我们自定义一个类People,然后用该类创建对象,就首先会加载改类到方法区。
假设你有一个应用需要的类叫作 Abc.class,首先加载这 个类的请求由 应用类加载器委托给它的父类加载器 Extension 类加载器, 然后再委托给 Bootstrap 类加载器;Bootstrap 类加载器 会先看看 rt.jar 中有没有 这个类,因为并没有这个类,所以这个请求又回到 Extension 类加载器,它会查 看 jre/lib/ext 目录下有没有这个类,如果这个类被 Extension 类加载器找到了, 那么它将被加载,而 应用类Application类加载器不会加载这个类;而如果这个类没有 被 Extension 类加载器找到,那么再由 应用类Application 类加载器从 classpath 中寻找, 如果没找到,就会抛出ClassNotFoundException异常。
如下图所示:

双亲委托模型
从1.2版本开始,Java引入了双亲委托模型,从而更好的保证Java平台的安全。
在此模型下,当一个装载器被请求装载某个类时,它首先委托自己的parent去装载,若parent能装载,则返回这个类所对应的Class对象;若parent不能装载,则由parent的请求者去装载。
为什么双亲委托模型更加安全?
因为在此模型下用户自定义的类装载器不可能装载应该由父类加载器加载的可靠类(如Extension加载器(jre/lib/ext)和Bootstrap加载器(rt.jar)下的class),从而防止不可靠甚至恶意的代码代替由父亲装载器装载的可靠代码。
比如用户自定义自己的一个名为String的恶意类,想要替换rt.jar下面java.lang.String,加载时,由于双全委托模型,首先请求到App ClassLoader,然后再到Extension ClassLoader,再到Bootstrap ClassLoader,由于已经加载过java.lang.String, java.lang包的String类不会再替换。
也就是重要的核心类和公共类都被Bootstrap和Extension加载了,不会被恶意类来替换这两个加载器加载的类。
Java类加载机制
类从被加载到虚拟机内存中开始,到卸载出内存为止,它的整个生命周期包括:加载(Loading)、验证(Verification)、准备(Preparation)、解析(Resolution)、初始化(Initialization)、使用(Using)和卸载(Unloading)7个阶段。其中准备、验证、解析3个部分统称为连接(Linking)。如图所示。

加载
在加载阶段(可以参考java.lang.ClassLoader的loadClass()方法),虚拟机需要完成以下3件事情:
1.通过一个类的全限定名(包名.类名吧)来获取定义此类的二进制字节流(并没有指明要从一个Class文件中获取,可以从其他渠道,譬如:网络、动态生成、数据库等);
2.将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构;
3.在内存中生成一个代表这个类的java.lang.Class对象,作为方法区中这个类的各种数据的访问入口;
验证
验证是连接阶段的第一步,这一阶段的目的是为了确保Class文件的字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机自身的安全。
验证阶段大致会完成4个阶段的检验动作:
- 文件格式验证:验证字节流是否符合Class文件格式的规范;例如:是否以魔术0xCAFEBABE开头、主次版本号是否在当前虚拟机的处理范围之内、常量池中的常量是否有不被支持的类型。
- 元数据验证:对字节码描述的信息进行语义分析(注意:对比javac编译阶段的语义分析),以保证其描述的信息符合Java语言规范的要求;例如:这个类是否有父类,除了java.lang.Object之外。
- 字节码验证:通过数据流和控制流分析,确定程序语义是合法的、符合逻辑的。
- 符号引用验证:确保解析动作能正确执行。
准备
准备阶段是正式为类变量分配内存并设置类变量初始值的阶段,这些变量所使用的内存都将在方法区中进行分配。这时候进行内存分配的仅包括类变量(被static修饰的变量),而不包括实例变量,实例变量将会在对象实例化时随着对象一起分配在堆中。其次,这里所说的初始值“通常情况”下是数据类型的零值,假设一个类变量的定义为:
|
public static int value=123; |
那变量value在准备阶段过后的初始值为0而不是123.因为这时候尚未开始执行任何java方法,而把value赋值为123的指令是程序被编译后,存放于类构造器()方法之中,所以把value赋值为123的动作将在初始化阶段才会执行。
至于“特殊情况”是指:public static final int value=123,即当类字段的字段属性是ConstantValue时,会在准备阶段初始化为指定的值,所以标注为final之后,value的值在准备阶段初始化为123而非0.
解析
解析阶段是虚拟机将常量池内的符号引用替换为直接引用的过程。解析动作主要针对类或接口、字段、类方法、接口方法、方法类型、方法句柄和调用点限定符7类符号引用进行。
初始化
类初始化阶段是类加载过程的最后一步,到了初始化阶段,才真正开始执行类中定义的java程序代码。
在准备阶段,变量已经付过一次系统要求的初始值,而在初始化阶段,则根据程序猿通过程序制定的计划去初始化类变量和其他资源,或者说:初始化阶段是执行类构造器<clinit>()方法的过程.
<clinit>()方法是由编译器自动收集类中的所有类变量的赋值动作和静态语句块static{}中的语句合并产生的,编译器收集的顺序是由语句在源文件中出现的顺序所决定的,静态语句块只能访问到定义在静态语句块之前的变量,定义在它之后的变量,在前面的静态语句块可以赋值,但是不能访问。如下:
|
1 2 3 4 5 6 7 8 9 |
public class Test { static { i=0; System.out.println(i);//这句编译器会报错:Cannot reference a field before it is defined(非法向前应用) } static int i=1; } |
一个例子
|
class SingleTon { private static SingleTon singleTon = new SingleTon(); public static int count1; public static int count2 = 0; private SingleTon() { count1++; count2++; } public static SingleTon getInstance() { return singleTon; } } public class Test { public static void main(String[] args) { SingleTon singleTon = SingleTon.getInstance(); System.out.println("count1=" + singleTon.count1); System.out.println("count2=" + singleTon.count2); } } |
分析:
1:SingleTon singleTon = SingleTon.getInstance();调用了类的SingleTon调用了类的静态方法,触发类的初始化
2:类加载的时候在准备过程中为类的静态变量分配内存并初始化默认值 singleton=null count1=0,count2=0
3:类初始化阶段,为类的静态变量赋值和执行静态代码块。singleton赋值为new SingleTon()调用类的构造方法
4:调用类的构造方法后count=1;count2=1
5:继续为count1与count2赋值,此时count1没有赋值操作,所以count1为1,但是count2执行赋值操作就变为0
Java反射中Class.forName和classloader的区别
Java中Class.forName和classloader都可以用来对类进行加载。
Class.forName除了将类的.class文件加载到jvm中之外,还会对类进行解释,执行类中的static块。
而ClassLoader 只干一件事情,就是将.class文件加载到jvm中,不会执行static中的内容,只有在newInstance才会去执行static块。
获得一个ClassLoader loader = ClassLoader.getSystemClassLoader();
Class.forName(name,initialize,loader)带参数也可控制是否加载static块。并且只有调用了newInstance()方法采用调用构造函数,创建类的对象。
static关键字,父类,子类,静态代码块
https://www.cnblogs.com/theRhyme/p/7538020.html
根据例子总结:
结果是集合{父类static,子类static,父类大括号,父类构造函数,子类大括号,子类构造函数}的一个子集。
核心就是类加载static块优先初始化,子类初始化会触发父类的初始化。
一个类中,被加载过的静态代码不会被重复加载。
{}大括号里的是初始化块,这里面的代码在创建java对象时执行,而且在构造器之前!其实初始化块就是构造器的补充,初始化块是不能接收任何参数的,定义的一些所有对象共有的属性、方法等内容时就可以用初始化块了初始化!! 好处是可以提高初始化块的复用,提高整个应用的可维护性。
对于静态字段,只有直接定义这个字段的类才会被初始化,因此通过其子类来引用父类中定义的静态字段,只会触发父类的初始化而不会触发子类的初始化。
@Autowired static变量
@Component
public class A{
@Autowired
private static RestTemplate restTemplate;
}
在Springframework里,我们是不能@Autowired一个静态变量,使之成为一个Spring bean的。因为当类加载器加载静态变量时,Spring上下文尚未加载。所以类加载器不会在bean中正确注入静态类,并且会失败。
解决思路:不要在类加载器加载静态变量时通过@Autowired初始化该静态变量,可以在后续@Autowired初始化该静态变量,比如构造方法,@PostConstruct等
双亲委派模式破坏-JDBC
JDBC之所以要破坏双亲委派模式是因为,JDBC的核心在rt.jar中由启动类加载器加载,而其实现则在各厂商实现的的jar包中,根据类加载机制,若A类调用B类,则B类由A类的加载器加载,也就是说启动类加载器要加载jar包下的类,我们都知道这是不可能的,启动类加载器负责加载$JAVA_HOME中jre/lib/rt.jar里所有的class,那么JDBC是如何加载这些Driver实现类的?
通过Thread.currentThread().getContextClassLoader()得到线程上下文加载器来加载Driver实现类。
TODO待写
参考来源:
https://blog.csdn.net/RogueFist/article/details/79575665
http://blog.csdn.net/stypace/article/details/40613953
http://www.importnew.com/18548.html
http://www.cnblogs.com/mangosoft/p/6485790.html
https://www.cnblogs.com/theRhyme/p/7538020.html
https://blog.csdn.net/sinat_34976604/article/details/86723663
https://www.cnblogs.com/aspirant/p/8991830.html
ClassLoader类加载器 & Java类加载机制 & 破坏双亲委托机制的更多相关文章
- JAVA基础|从Class.forName初始化数据库到SPI破坏双亲委托机制
代码托管在:https://github.com/fabe2ry/classloaderDemo 初始化数据库 如果你写过操作数据库的程序的话,可能会注意,有的代码会在程序的开头,有Class.for ...
- 破坏双亲委托机制的一些情况---Tomcat和JDBC,破坏后的安全问题
采用双亲委托机制的原因 类加载器就是将字节码搬进jvm方法区的组件.我们知道,JVM识别加载进来的类是通过类加载器+类全名完成的,也就是说同一个类由不同类加载器加载进去的话就会被视为不同的类.jdk提 ...
- jdbc 加载数据库驱动如何破坏双亲委托模式
导读 通过jdbc链接数据库,是每个学习Java web 方向的人必然一开始会写的代码,虽然现在各路框架都帮大家封装好了jdbc,但是研究一下jdbc链接的套路还是很意义 术语以及相 ...
- Java 类加载体系之 ClassLoader 双亲委托机制
Java 类加载体系之 ClassLoader 双亲委托机制 java 是一种类型安全的语言,它有四类称为安全沙箱机制的安全机制来保证语言的安全性,这四类安全沙箱分别是: 类加载体系 .class文件 ...
- JVM 类加载器的双亲委托机制
1.类加载器的层次结构 在双亲委托机制中,各个加载器按照父子关系形成了树形结构(逻辑意义),除了根加载器之外,其余的类加载器都有且只有一个父加载器. public class MyTest13 { p ...
- 【JVM进阶之路】十四:类加载器和类加载机制
在上一章里,我们已经学习了类加载的过程,我们知道在加载阶段需要"通过一个类的全限定名来获取描述该类的二进制字节流",而来完成这个工作的就是类加载器(Class Loader). 1 ...
- java类加载器-系统类加载器
系统类加载器 系统类加载器可能都耳详能熟,但是为了完整点,还是先简单的说说系统的类加载器吧. public class Test { public static void main(String[] ...
- 【Java虚拟机8】自定义类加载器、类加载器命名空间、类的卸载
前言 学习类加载器就一定要自己实现一个类加载器,今天就从一个简单的自定义类加载器说起. 自定义类加载器 例1 一个简单的类加载器,从一个给定的二进制名字读取一个字节码文件的内容,然后生成对应的clas ...
- java类加载器-Tomcat类加载器
在上文中,已经介绍了系统类加载器以及类加载器的相关机制,还自定制类加载器的方式.接下来就以tomcat6为例看看tomat是如何使用自定制类加载器的.(本介绍是基于tomcat6.0.41,不同版本可 ...
随机推荐
- QT 安装 配置过程
QT, QT creator的安装,环境配置: 需要根据这个连接重新梳理一遍:https://blog.csdn.net/win_turn/article/details/50465127 1)一种方 ...
- web版本的用户登陆票据 FormsAuthenticationTicket
FormsAuthenticationTicket ticket = new FormsAuthenticationTicket(1, "username", DateTime.N ...
- 【C++札记】const关键字
C++中const关键字无处不在,我这里做下汇总,作为工具文章方便翻阅. 一:修饰数据成员 修饰的成员一单定义初始化后不能再进行修改,如: const int a = 10; a =20; //重新赋 ...
- SQL——ORDER BY关键字
一.ORDER BY关键字用法 ORDER BY关键字用于对数据进行排序,默认ASC(升序),可以DESC关键字变为降序. ORDER BY关键字语法: SELECT * from 表名 WHERE ...
- 模型层之ORM、数据库和单表操作
一.ORM简介 ORM是“对象-关系-映射”的简称,一般指持久化数据和实体对象的映射 1.1 什么是“持久化” 持久(Persistence),即把数据(如内存中的对象)保存到可永久保存的存储设备中( ...
- 神奇的print
一:多看看 1. #大小写转换 ,有大写的 全转化为大写 s = 'fds Kkg' print(s.swapcase()) #下划线等各种插入 s = 'fdsfkg' print('_'.join ...
- Fully Convolutional Networks for Semantic Segmentation 译文
Fully Convolutional Networks for Semantic Segmentation 译文 Abstract Convolutional networks are powe ...
- ORACLE存储过程,循环语法和游标
1.定义所谓存储过程(Stored Procedure),就是一组用于完成特定数据库功能的SQL语句集,该SQL语句集经过编译后存储在数据库系统中.在使用时候,用户通过指定已经定义的存储过程名字并给出 ...
- mybatis-plus代码生成,实体类不生成父类属性
一.参考文档: 官方文档其实说的很清楚了,可能有个别地方有点不太清楚. mybatis-plus官方: https://mp.baomidou.com/guide/generator.html 模版引 ...
- 借助openpyxl处理excel
一次处理excel中,原计划是借助excel中自带的工具进行处理,然而看到需要处理的列要达到30+,后来放弃了,用Python处理或许是一个不错的选择. 需求: 表格中每一列数据都是一个随机值,但是已 ...