JVM体系结构之二:类加载器之2:JVM 自定义的类加载器的实现和使用
一、回顾一下jdk自带的类加载器:
1.java虚拟机自带的加载器
根类加载器(Bootstrap,c++实现)
扩展类加载器(Extension,java实现)
应用类加载器(App,java实现)
2.用户自定义的类加载器
java.lang.ClassLoader的子类
用户可以定制类的加载方式
java的类加载机制默认情况下是采用委托模型:当加载某个类时JVM会首先尝试用当前类加载器的父类加载器加载该类,若父类加载器加载不到再由当前类加载器来加载,因此这种模型又叫做“父优先”模型。
但是在实际项目中我们可能会要求先从当前类加载加载再从父类加载器加载,如项目中的某类的版本可能和container中的不一致的时候,若还从container加载就会报jar包冲突的异常,实际上jar包冲突的问题在实际开发过程中是经常会遇到的。
解决方案是通过扩展自定义的ClassLoader,重写loadClass方法,先从当前类加载器加载再从父类加载器加载。
二、用户自定义的类加载器:
要创建用户自己的类加载器,只需要扩展java.lang.ClassLoader类,然后覆盖它的findClass(String name)方法即可,该方法根据参数指定类的名字,返回对应的Class对象的引用。
protected Class findClass(String s)
throws ClassNotFoundException
{
throw new ClassNotFoundException(s);
}
示例:创建3个自定义类加载器,分别对应的父加载器如下图:

自定义类加载器:
package com.dxz.classloader; import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream; public class MyClassLoader extends ClassLoader { // 类加载器名称
private String name;
// 加载类的路径
private String path = "E:/";
private final String fileType = ".class"; public MyClassLoader(String name) {
// 让系统类加载器成为该类加载器的父加载器
super();
this.name = name;
} public MyClassLoader(ClassLoader parent, String name) {
// 显示指定该类加载器的父加载器
super(parent);
this.name = name;
} public String getPath() {
return path;
} public void setPath(String path) {
this.path = path;
} @Override
public String toString() {
return this.name;
} /**
* 获取.class文件的字节数组
*
* @param name
* @return
*/
private byte[] loaderClassData(String className) {
InputStream is = null;
byte[] data = null;
ByteArrayOutputStream baos = new ByteArrayOutputStream();
className = className.replace(".", "/");
try {
is = new FileInputStream(new File(path + className + fileType));
int c = 0;
while (-1 != (c = is.read())) {
baos.write(c);
}
data = baos.toByteArray(); } catch (Exception e) {
e.printStackTrace();
} finally {
try {
is.close();
baos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
return data;
} /**
* 获取Class对象
*/
@Override
public Class<?> findClass(String name) {
byte[] data = loaderClassData(name);
return this.defineClass(name, data, 0, data.length);
} public static void main(String[] args)
throws ClassNotFoundException, InstantiationException, IllegalAccessException {
// loader1的父加载器为系统类加载器
MyClassLoader loader1 = new MyClassLoader("loader1");
loader1.setPath("E:/duan/lib1/");
// loader2的父加载器为loader1
MyClassLoader loader2 = new MyClassLoader(loader1, "loader2");
loader2.setPath("E:/duan/lib2/");
// loader3的父加载器为根类加载器
MyClassLoader loader3 = new MyClassLoader(null, "loader3");
loader3.setPath("E:/duan/lib3/"); Class clazz = loader2.loadClass("com.dxz.classloader.Sample");
Object object = clazz.newInstance();
//object.main(null);
}
}
package com.dxz.classloader;
public class Hello {
public Hello() {
System.out.println("hello");
}
}
被加载类-情况1:
package com.dxz.classloader;
public class Sample {
private String name = "hello world";
public Sample() {
System.out.println("one");
new Hello();
}
public static void main(String[] args) {
System.out.println(new Sample().name);
}
}
并将生成的Sample.class文件拷贝到指定的目录下:

被加载类-情况2:
package com.dxz.classloader;
public class Sample {
private String name = "hello world";
public Sample() {
System.out.println("two");
new Hello();
}
public static void main(String[] args) {
System.out.println(new Sample().name);
}
}
将新生成的Sample.class文件拷贝lib2下

被加载类-情况3:
package com.dxz.classloader;
public class Sample {
private String name = "hello world";
public Sample() {
System.out.println("three");
new Hello();
}
public static void main(String[] args) {
System.out.println(new Sample().name);
}
}
将新生成的Sample.class文件拷贝lib3下

测试场景1:
// loader1的父加载器为系统类加载器
MyClassLoader loader1 = null;//new MyClassLoader("loader1");
//loader1.setPath("E:/duan/lib1/");
// loader2的父加载器为loader1
MyClassLoader loader2 = new MyClassLoader(loader1, "loader2");
loader2.setPath("E:/duan/lib2/");
如果loader2的父加载器为空,则loader2加载器会去加载E:/duan/lib2/下的Sample.class文件,结果如下:

测试场景2:
// loader1的父加载器为系统类加载器
MyClassLoader loader1 = new MyClassLoader("loader1");
loader1.setPath("E:/duan/lib1/");
// loader2的父加载器为loader1
MyClassLoader loader2 = new MyClassLoader(loader1, "loader2");
loader2.setPath("E:/duan/lib2/");
结果:

结果说明:
当执行loader2.loaderClass("com.dxz.classloader.Sample")时,先由它上层的所有父加载器尝试加载Sample类。如果父加载loader1不为空,则loader1从D:/lib1/目录下成功的加载了Sample类,因此laoder1是Sample类的定义类加载器,loader1和loader2是Sample类的初始类加载器。
当执行loader3.loadClass("com.dxz.classloader.Sample")时,先由它上层的所有父加载器尝试加载Sample类。loader3的父加载器为根类加载器,它无法加载Sample类,接着loader3从D:/lib3/目录下成功地加载了Sample类,因此loader3是Sample类的定义类加载器即初始类加载器。

在Sample类中主动new Hello类,当执行Sample类的构造方法中的new Hello()语句时,Java虚拟机需要先加载Hello类,Java虚拟机会按Sample类的定义类加载器去加载Hello类,加载过程也同样采用父亲委托机制。
三、总结
自定义类加载器的核心在于对字节码文件的获取,如果是加密的字节码则需要在该类中对文件进行解密。由于这里只是演示,我并未对class文件进行加密,因此没有解密的过程。这里有几点需要注意:
1、这里传递的文件名需要是类的全限定性名称,即com.paddx.test.classloading.Test格式的,因为 defineClass 方法是按这种格式进行处理的。
2、最好不要重写loadClass方法,因为这样容易破坏双亲委托模式。
3、这类 Test 类本身可以被 AppClassLoader 类加载,因此我们不能把 com/paddx/test/classloading/Test.class 放在类路径下。否则,由于双亲委托机制的存在,会直接导致该类由 AppClassLoader 加载,而不会通过我们自定义类加载器来加载。
双亲委派机制能很好地解决类加载的统一性问题。对一个 Class 对象来说,如果类加载器不同,即便是同一个字节码文件,生成的 Class 对象也是不等的。也就是说,类加载器相当于 Class 对象的一个命名空间。双亲委派机制则保证了基类都由相同的类加载器加载,这样就避免了同一个字节码文件被多次加载生成不同的 Class 对象的问题。但双亲委派机制仅仅是Java 规范所推荐的一种实现方式,它并不是强制性的要求。近年来,很多热部署的技术都已不遵循这一规则,如 OSGi 技术就采用了一种网状的结构,而非双亲委派机制。
JVM体系结构之二:类加载器之2:JVM 自定义的类加载器的实现和使用的更多相关文章
- JVM体系结构之二:类加载器
一.概述 定义:虚拟机把描述类的数据从Class文件加载到内存,并对数据进行校验.转换解析和初始化,最终形成可以被虚拟机直接使用的Java类型.类加载和连接的过程都是在运行期间完成的. 二. 类的加载 ...
- JVM学习十二 - (复习)JVM内存结构
JVM 内存结构 Java 虚拟机的内存空间分为 5 个部分: 程序计数器 Java 虚拟机栈 本地方法栈 堆 方法区 JDK 1.8 同 JDK 1.7 比,最大的差别就是:元数据区取代了永久代.元 ...
- JVM 体系结构与工作方式
.katex { display: block; text-align: center; white-space: nowrap; } .katex-display > .katex > ...
- JVM学习二:JVM之类加载器之加载分析
前面一遍,我们对类的加载有了一个整体的认识,而这一节我们细节分析一下类加载器的第一步,即:加载. 一.概念 类的加载指的是将类的.class文件中的二进制数据读入到内存中,将其放在运行时数据区的方法区 ...
- java之jvm学习笔记二(类装载器的体系结构)
java的class只在需要的时候才内转载入内存,并由java虚拟机的执行引擎来执行,而执行引擎从总的来说主要的执行方式分为四种, 第一种,一次性解释代码,也就是当字节码转载到内存后,每次需要都会重新 ...
- JVM学习六:JVM之类加载器之双亲委派机制
前面我们知道类加载有系统自带的3种加载器,也有自定义的加载器,那么这些加载器之间的关系是什么,已经在加载类的时候,谁去加载呢?这节,我们将进行讲解. 一.双亲委派机制 JVM的ClassLoader采 ...
- JVM系列(二)之类加载
什么是类的加载 类加载是指将源代码编译后的.class加载到内存中初始化待程序使用的过程,类加载的最终结果就是将.class字节码加载到JVM中生成一个java.lang.Class对象,提供给程序使 ...
- JVM 体系结构
JVM 是一种抽象的计算机,基于堆栈架构,它有自己的指令集和内存管理,是 Java 跨平台的依据,JVM解释执行字节码,或将字节码编译成本地代码执行.Java 虚拟机体系结构如下: Class Fil ...
- JVM体系结构-----深入理解内存结构
一.概述 内存在计算机中占据着至关重要的地位,任何运行时的程序或者数据都需要依靠内存作为存储介质,否则程序将无法正常运行.与C和C++相比,使用Java语言编写的程序并不需要显示的为每一个对象编写对应 ...
随机推荐
- mysql怎么在已建好的表中添加自增序列
alter table 表明 change id id int not null auto_increment unique;
- HackerRank - camelcase 【字符串】
思路 找单词 第一个 单词 是小写 然后 后面的单词 第一位 都是大写 刚开始 初始化 ans = 1 然后 往后遍历 碰到 大写的 更新答案 AC代码 #include <cstdio> ...
- 转 开启“大数据”时代--大数据挑战与NoSQL数据库技术 iteye
一直觉得“大数据”这个名词离我很近,却又很遥远.最近不管是微博上,还是各种技术博客.论坛,碎碎念大数据概念的不胜枚举. 在我的理解里,从概念理解上来讲,大数据的目的在于更好的数据分析,否则如此大数据的 ...
- Effective java -- 7 通用程序设计
第四十五条:将局部变量的作用域最小化 第四十六条:加强版for循环优于传统for循环 第四十七条:了解和使用类库书中提到了一个生成随机数的例子.正好需要. public static void mai ...
- 【leetcode刷题笔记】Longest Common Prefix
Write a function to find the longest common prefix string amongst an array of strings. 题解:以strs[0]为模 ...
- 3像素文本偏移bug 解决方案
<style>.box1{ width:100px; height:50px; float:left; background:#dedede;_margin-right:-3px;}.bo ...
- HTTPS协议原理透析
1.HTTPS本身并非协议,而是标准的HTTP协议架在SSL/TLS协议之上的一种结构.(一种不太合适的说法可以认为是两种协议的叠加).HTTP是工作在OSI7层模型的最上层,就是第7层:Applic ...
- 剑指offer之 整数中1出现的次数
public class Solution { public int NumberOf1Between1AndN_Solution(int n) { int count=0; for(int i=1; ...
- Jquery的ajax获取action中的返回值
js部分: function check() { $.ajax({ type : "POST", url : "myCloudWantseeListHD ...
- 分享知识-快乐自己:springboot之thymeleaf (1):简单的thymeleaf例子
之前搞springboot时,发现spring很推荐thymeleaf,所以看了看学了学,感觉不错,做个笔记先. 做个简单和例子,项目是springboot,所以引入themeleaf相关包 pom. ...