前言

学习类加载器就一定要自己实现一个类加载器,今天就从一个简单的自定义类加载器说起。

自定义类加载器

例1

一个简单的类加载器,从一个给定的二进制名字读取一个字节码文件的内容,然后生成对应的class对象。

package com.jamie.jvmstudy;

import java.io.*;

public class CustomizedClassLoader extends ClassLoader {

    private String classLoaderName;

    private String fileExtension = ".class";

    public CustomizedClassLoader(String classLoaderName) {
super(); //如果调用默认构造器,代表默认的父类加载器是系统类加载器SystemClassLoader
this.classLoaderName = classLoaderName;
} public CustomizedClassLoader(ClassLoader parent, String classLoaderName) {
super(parent); //如果指定父类加载器,那么该构造器执行完之后,这个类加载器就有指定的parent了。(默认是系统类加载器)
this.classLoaderName = classLoaderName;
} @Override
public Class<?> findClass(String className) throws ClassNotFoundException {
byte[] data = this.loadClassData(className); return this.defineClass(className, data, 0, data.length);
} private byte[] loadClassData(String className) {
byte[] data = null;
try(InputStream is = new FileInputStream(new File(className + this.fileExtension));
ByteArrayOutputStream baos = new ByteArrayOutputStream()) {
int ch;
while(-1 != (ch = is.read())) {
baos.write(ch);
}
data = baos.toByteArray();
} catch (Exception e) {
e.printStackTrace();
}
return data;
} public static void main(String[] args) throws Exception {
CustomizedClassLoader customizedClassLoader = new CustomizedClassLoader("jamie loader");
test(customizedClassLoader);
} private static void test(ClassLoader classLoader) throws Exception {
Class<?> myClass = classLoader.loadClass("com.jamie.jvmstudy.TestClassLoader");
Object o = myClass.newInstance();
System.out.println(String.format("classLoader in this method is [%s]", classLoader));
System.out.println(String.format("object [%s] has been created by [%s].", o, myClass.getClassLoader()));
System.out.println(String.format("class loader of CustomizedClassLoader is [%s]", classLoader.getClass().getClassLoader()));
}
}

运行结果如下:

classLoader in this method is [com.jamie.jvmstudy.CustomizedClassLoader@4b67cf4d]
object [com.jamie.jvmstudy.TestClassLoader@7ea987ac] has been created by [sun.misc.Launcher$AppClassLoader@14dad5dc].
class loader of CustomizedClassLoader is [sun.misc.Launcher$AppClassLoader@14dad5dc]

这里重点说明一下:示例中的"com.jamie.jvmstudy.TestClassLoader"Class是被系统类加载器加载的,而不是我们自定义的加载器。

原因:

  1. 自定义的加载器CustomizedClassLoader的父类构造器是系统类加载器。因为我们加载类调用的方法是:classLoader.loadClass("com.jamie.jvmstudy.TestClassLoader")
  2. 默认的loadClass()方法实现就是双亲委派的源码实现,因为系统类加载器会在当前的classpath(类路径)下查找是否存在匹配的"binary name",如果存在,则系统类加载器加载成功。
  3. 所以只要类路径下存在匹配的二进制名字的字节码,就会被系统类加载器成功加载。

例2

基于例1的基础上,自定义加载器获取字节码文件的内容改为从一个指定的路径中读取。并且传入一个非classpath的路径,去加载某个字节码。

因为自定义的类加载器默认的父类加载器是系统类加载器,运行下例的时候,需要在编译之后把类路径下的com.jamie.jvmstudy.TestClassLoader字节码文件删除(防止[双亲委托机制]使[系统类加载器AppClassLoader]把指定类加载进虚拟机),然后运行本例。

package com.jamie.jvmstudy;

import java.io.*;

public class CustomizedClassLoader extends ClassLoader {

    private String classLoaderName;

    private String path;

    private String fileExtension = ".class";

    public CustomizedClassLoader(String classLoaderName) {
super(); //如果调用默认构造器,代表默认的父类加载器是系统类加载器SystemClassLoader
this.classLoaderName = classLoaderName;
} public CustomizedClassLoader(ClassLoader parent, String classLoaderName) {
super(parent);
this.classLoaderName = classLoaderName;
} @Override
public Class<?> findClass(String className) throws ClassNotFoundException {
System.out.println("Self findClass() invoked");
byte[] data = this.loadClassData(className);
return this.defineClass(className, data, 0, data.length);
} private byte[] loadClassData(String className) {
byte[] data = null;
className = className.replace(".", "/");
try(InputStream is = new FileInputStream(new File(path + className + this.fileExtension));
ByteArrayOutputStream baos = new ByteArrayOutputStream()) {
int ch;
while(-1 != (ch = is.read())) {
baos.write(ch);
}
data = baos.toByteArray();
} catch (Exception e) {
e.printStackTrace();
}
return data;
} public static void main(String[] args) throws Exception {
CustomizedClassLoader loader1 = new CustomizedClassLoader("jamie loader1");
loader1.setPath("D:/temp/");
Class<?> myClass1 = loader1.loadClass("com.jamie.jvmstudy.TestClassLoader");
System.out.println(String.format("Hashcode of myClass1 is [%s].", myClass1.hashCode()));
System.out.println(String.format("myClass1 is [%s]", myClass1));
System.out.println(); CustomizedClassLoader loader2 = new CustomizedClassLoader("jamie loader2");
loader2.setPath("D:/temp/");
Class<?> myClass2 = loader2.loadClass("com.jamie.jvmstudy.TestClassLoader");
System.out.println(String.format("Hashcode of myClass2 is [%s].", myClass2.hashCode()));
System.out.println(String.format("myClass2 is [%s]", myClass2)); System.out.println();
System.out.println("myClass1 == myClass2 ? " + (myClass1 == myClass2));
} public void setPath(String path) {
this.path = path;
}
}

运行结果如下:

Self findClass() invoked
Hashcode of myClass1 is [1956725890].
myClass1 is [class com.jamie.jvmstudy.TestClassLoader] Self findClass() invoked
Hashcode of myClass2 is [21685669].
myClass2 is [class com.jamie.jvmstudy.TestClassLoader] myClass1 == myClass2 ? false

结论:由运行结果可以看出,此时的虚拟机中出现了两个不一样的TestClassLoader.class对象。

这就引入了类加载器的命名空间的问题。

我们之前理解的“Class对象只存在一份”是基于同一个类加载器的命名空间来说的。

类加载器的命名空间

  • 每个类加载器都有自己的命名空间。命名空间由该加载器和所有父加载器所加载的类组成。
  • 在同一个命名空间中,不会出现类的完整名字(包括类的包名)相同的两个类。
  • 在不同的命名空间中,有可能会出现类的完整名字(包括类的包名)相同的两个类。

后续写了一篇文章类加载器之命名空间详解,里面有对【命名空间】更加详细的分析与示例。感兴趣请跳转。

类的卸载

  • 当一个类被加载、连接和初始化之后,它的生命周期就开始了。当代表该类的Class对象不再被引用,既不可达时,Class对象就会结束生命周期,该类在方法区内的数据也会被卸载,从而结束该类的生命周期。
  • 一个类何时结束生命周期,取决于代表它的Class对象何时结束生命周期。
  • 被Java虚拟机自带的ClassLoader加载的类,是不会被卸载的。因为JVM本身会始终引用这些ClassLoader,而这些ClassLoader始终会引用它加载的所有类的Class对象。所以它们永远可达。
  • 只有自定义的类加载器加载的类,才有可能被卸载。

类卸载的证明:

基于上述例2,改造一下main方法,并在执行时,添加JVM运行参数:-XX:+TraceClassUnloading

    public static void main(String[] args) throws Exception {
CustomizedClassLoader loader1 = new CustomizedClassLoader("jamie loader1");
loader1.setPath("D:/temp/");
Class<?> myClass1 = loader1.loadClass("com.jamie.jvmstudy.TestClassLoader");
System.out.println(String.format("Hashcode of myClass1 is [%s].", myClass1.hashCode()));
System.out.println(String.format("myClass1 is [%s]", myClass1));
System.out.println(); loader1 = null;
myClass1 = null;
System.gc(); CustomizedClassLoader loader2 = new CustomizedClassLoader("jamie loader2");
loader2.setPath("D:/temp/");
Class<?> myClass2 = loader2.loadClass("com.jamie.jvmstudy.TestClassLoader");
System.out.println(String.format("Hashcode of myClass2 is [%s].", myClass2.hashCode()));
System.out.println(String.format("myClass2 is [%s]", myClass2)); System.out.println();
System.out.println("myClass1 == myClass2 ? " + (myClass1 == myClass2));
Thread.sleep(50000); //为了查看类的卸载情况增加延时
}

运行结果:可以看到TestClassLoader类被卸载一次。

=====Self findClass() invoked=====
Hashcode of myClass1 is [1956725890].
myClass1 is [class com.jamie.jvmstudy.TestClassLoader] [Unloading class com.jamie.jvmstudy.TestClassLoader 0x00000007c0061028]
=====Self findClass() invoked=====
Hashcode of myClass2 is [21685669].
myClass2 is [class com.jamie.jvmstudy.TestClassLoader] myClass1 == myClass2 ? false

增加main方法睡眠时间,使用Java VisualVM可以看到下图:

最新文章

  1. HTML 5 音频
  2. JAVA 1.1
  3. 5月21日 CSS样式表加阴影
  4. C++@类对象和类指针的区别(转)
  5. Objective-C 内存管理原则
  6. dom 回到顶部(兼容IE FF Chrome)
  7. Php 输出语句
  8. C语言陷阱——类型转换
  9. android ImageView scaleType属性(转)
  10. 闪存主控IC的作用
  11. [android] 百度地图开发 (两).所在地的城市定位和城市POI搜索
  12. oracle sql 分页
  13. echarts添加点击事件
  14. 数据定义语言(DDL Data Definition Language)基础学习笔记
  15. Dynamics CRM On-Premise V9安装手记
  16. 漫画|Linux 并发、竞态、互斥锁、自旋锁、信号量都是什么鬼?(转)
  17. 面试(一)-HashMap
  18. [swoole]swoole常见问题总汇
  19. Dart语言快速学习上手(新手上路)
  20. Windows下使用命令安装Python的scipy库出错的解决

热门文章

  1. CodeReview杂谈
  2. B. 2194: 快速傅立叶之二解题报告
  3. 痞子衡嵌入式:在MDK开发环境下将关键函数重定向到RAM中执行的几种方法
  4. DataGridView高度自动调整
  5. 【转载】小心 int 乘法溢出!
  6. XSS注入
  7. 引人遐想,用 Python 获取你想要的 “某个人” 摄像头照片
  8. vue报错 Uncaught TypeError: Cannot read property of null
  9. 告别Kafka Stream,让轻量级流处理更加简单
  10. mogoose 创建数据库并增删改查