一、回顾一下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. Mac OS X 软件推荐

    ​1. 前言 每个操作系统都有自己的一套软件系统,但是不同的用户却会有不同的需求,系统虽会为用户提供一些基础软件,不过为了能无碍的进入自己的学习和工作状态,总有一些软件是必须安装的,同时这些软件也可以 ...

  2. 制作输入框(Input)

    怎样判断是否应当使用输入框 输入框,就是用户可以自由输入文本的地方.当需要判断是否需要使用输入框时,可以遵循一条原则:凡是需要用户自主输入文本的地方,几乎都必须使用输入框. 输入框的常见用法:输入登录 ...

  3. WebView重定向新开界面问题-b

    首先介绍下这个问题,iOS上WebView 如果想更贴近native,就要加载新URL的时候新开个界面,但是如果加载的链接有重定向的话,就会在中间开一个空白的界面,这个好烦.然后就是解决这个问题,采用 ...

  4. iOS基本网络请求

    常见的网络请求有同步GET, 同步POST, 异步GET, 异步POST. GET请求和POST请求的区别: 1. GET请求的接口会包含参数部分,参数会作为网址的一部分,服务器地址与参数之间通过 ? ...

  5. 然爸读书笔记(2013-5)----Rework(重来)

    (1)你没有必要耗尽你一生的积蓄,承担财务风险. (2)你可以一边继续日常工作,一边开始创业,这样随时都能有现金满足需要.你甚至不需要办公室. 现在可以在家工作,和从未见面离你千里之外的人合作. (3 ...

  6. storm sum aggregate 原语 聚合 本地测试

    编写storm程序,对数据进行聚合并且写入到mysql, 本文  主要说明数据中有多个字段需要进行sum或其他操作时的程序写法 1.主程序main方法,storm 拓扑运行入口 public clas ...

  7. 6779. Can you answer these queries VII - SPOJ

    Given a tree with N ( N<=100000 ) nodes. Each node has a interger value x_i ( |x_i|<=10000 ). ...

  8. 谈谈js中for in 需要注意的地方

    js中for in 可以遍历对象或数组的显性属性,也就是说我们自己定义的属性是可以遍历的,那些原型上默认已有的属性,例如:Object.prototype.toString.Object.protot ...

  9. C++开发必看 四种强制类型转换的总结

    C风格的强制类型转换(Type Cast)很简单,不管什么类型的转换统统是: TYPE b = (TYPE)a C++风格的类型转换提供了4种类型转换操作符来应对不同场合的应用. const_cast ...

  10. 【形式化方法:VDM++系列】2.VDMTools环境的搭建

    接前文:http://www.cnblogs.com/Kassadin/p/3975853.html 上次讲了软件需求分析的演化过程,本次进入正题——VDM开发环境的搭建 (自从发现能打游戏以来,居然 ...