一、回顾一下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 技术就采用了一种网状的结构,而非双亲委派机制。

Java虚拟机笔记 – JVM 自定义的类加载器的实现和使用2的更多相关文章

  1. JVM体系结构之二:类加载器之2:JVM 自定义的类加载器的实现和使用

    一.回顾一下jdk自带的类加载器: 1.java虚拟机自带的加载器     根类加载器(Bootstrap,c++实现)     扩展类加载器(Extension,java实现)     应用类加载器 ...

  2. 【Java虚拟机11】线程上下文类加载器

    前言 目前学习到的类加载的知识,都是基于[双亲委托机制]的.那么JDK难道就没有提供一种打破双亲委托机制的类加载机制吗? 答案是否定的. JDK为我们提供了一种打破双亲委托模型的机制:线程上下文类加载 ...

  3. Java虚拟机JVM学习05 类加载器的父委托机制

    Java虚拟机JVM学习05 类加载器的父委托机制 类加载器 类加载器用来把类加载到Java虚拟机中. 类加载器的类型 有两种类型的类加载器: 1.JVM自带的加载器: 根类加载器(Bootstrap ...

  4. 【Java虚拟机8】自定义类加载器、类加载器命名空间、类的卸载

    前言 学习类加载器就一定要自己实现一个类加载器,今天就从一个简单的自定义类加载器说起. 自定义类加载器 例1 一个简单的类加载器,从一个给定的二进制名字读取一个字节码文件的内容,然后生成对应的clas ...

  5. Java虚拟机笔记(一):类加载机制

    一.概述 虚拟机把描述类的数据从Class文件加载到内存,并对数据进行校验.转换解析和初始化,最终形成可以被虚拟机直接使用的Java类型,这就是虚拟机的类加载机制. 二.类加载的生命周期 类从被加载到 ...

  6. java虚拟机学习-JVM内存管理:深入垃圾收集器与内存分配策略(4)

    Java与C++之间有一堵由内存动态分配和垃圾收集技术所围成的高墙,墙外面的人想进去,墙里面的人却想出来. 概述: 说起垃圾收集(Garbage Collection,下文简称GC),大部分人都把这项 ...

  7. Java虚拟机笔记(五):JVM中对象的分代

    为什么要分代 为什么需要把堆分代?不分代不能完成他所做的事情么?其实不分代完全可以,分代的唯一理由就是优化GC性能.你先想想,如果没有分代,那我们所有的对象都在一块,GC的时候我们要找到哪些对象没用, ...

  8. Java虚拟机笔记(四):垃圾收集器

    前言 前一篇文章介绍了内存的垃圾收集算法,现在介绍下内存回收的具体实现--垃圾收集器. 由于Java虚拟机规范中对垃圾收集器应该如何实现并没有任何规定,因此不同的厂商,不同版本的虚拟机所提供的垃圾收集 ...

  9. 读书笔记-《深入理解Java虚拟机:JVM高级特性与最佳实践》

    目录 概述 第一章: 走进Java 第二章: Java内存区域与内存溢出异常 第三章: 垃圾收集器与内存分配策略 第四章: 虚拟机性能监控与故障处理 第五章: 调优案例分析与实战 第六章: 类文件结构 ...

随机推荐

  1. iOS 进阶 第一天(0323)

    0323 Storyboard连线错误 如下图: 不允许直接修改对象的结构体属性成员,但允许直接整体修改对象的结构体属性 如下图: 打印一个控件对象的frame 如下图: 如果一个控件无论怎么改变它的 ...

  2. ajax加php实现三级联动

    js代码 <script type="text/javascript">    function get_next(t,pid){  //当前元素的id,当前optio ...

  3. 2002: [Hnoi2010]Bounce 弹飞绵羊 - BZOJ

    Description 某天,Lostmonkey发明了一种超级弹力装置,为了在他的绵羊朋友面前显摆,他邀请小绵羊一起玩个游戏.游戏一开始,Lostmonkey在地上沿着一条直线摆上n个装置,每个装置 ...

  4. 1050 棋盘染色 2 - Wikioi

    题目描述 Description 有一个5*N的棋盘,棋盘中的一些格子已经被染成了黑色,你的任务是对最少的格子染色,使得所有的黑色能连成一块. 输入描述 Input Description 第一行一个 ...

  5. 动态内存 this指针

    #include <iostream> #include <string> class Company { public: Company(std::string theNam ...

  6. PAT-乙级-1019. 数字黑洞 (20)

    1019. 数字黑洞 (20) 时间限制 100 ms 内存限制 65536 kB 代码长度限制 8000 B 判题程序 Standard 作者 CHEN, Yue 给定任一个各位数字不完全相同的4位 ...

  7. WCF 绑定的选择

    选自<WCF服务编程中文版> 第一章 WCF基础 绑定服务之间的通信方式是多种多样的,有多种可能的通信模式.包括:同步的请求/ 应答(Request/Reply)消息,或者异步的“即发即弃 ...

  8. HDU 1062 Text Reverse

    题意 : 给出你一个句子,让你把句子中每个单词的字母顺序颠倒一下输出. 思路 : 用栈即可,就是注意原来在哪儿有空格就要输出空格. //hdu1062 #include <iostream> ...

  9. codeforces #305 B Mike and Feet

    跟之前做过的51Nod的移数博弈是一样的QAQ 我们考虑每个数的贡献 定义其左边第一个比他小的数的位置为L 定义其右边第一个比他小的数的位置为R 这个可以用排序+链表 或者 单调队列 搞定 那么对于区 ...

  10. [itint5]字符串匹配

    http://www.itint5.com/oj/#15 用hash来做,目前为止做到最好也是case16超时(20w的规模),即使分桶也超时.注意计算hashcode时,'a'要算成1,否则如果'a ...