获得ClassLoader的途径

  • 获得当前类的ClassLoader

    • clazz.getClassLoader()
  • 获得当前线程上下文的ClassLoader
    • Thread.currentThread().getContextClassLoader();
  • 获得系统的ClassLoader
    • ClassLoader.getSystemClassLoader()
  • 获得调用者的ClassLoader
    • DriverManager.getCallerClassLoader

ClassLoader源码解析

概述

类加载器是用于加载类的对象,ClassLoader是一个抽象类。如果我们给定了一个类的二进制名称,类加载器应尝试去定位或生成构成定义类的数据。一种典型的策略是将给定的二进制名称转换为文件名,然后去文件系统中读取这个文件名所对应的class文件。

每个Class对象都会包含一个定义它的ClassLoader的一个引用。

数组类的Class对象,不是由类加载器去创建的,而是在Java运行期JVM根据需要自动创建的。对于数组类的类加载器来说,是通过Class.getClassLoader()返回的,与数组当中元素类型的类加载器是一样的;如果数组当中的元素类型是一个原生类型,数组类是没有类加载器的【代码一】。

应用实现了ClassLoader的子类是为了扩展JVM动态加载类的方式。

类加载器典型情况下时可以被安全管理器所使用去标识安全域问题。

ClassLoader类使用了委托模型来寻找类和资源,ClassLoader的每一个实例都会有一个与之关联的父ClassLoader,当ClassLoader被要求寻找一个类或者资源的时候,ClassLoader实例在自身尝试寻找类或者资源之前会委托它的父类加载器去完成。虚拟机内建的类加载器,称之为启动类加载器,是没有父加载器的,但是可以作为一个类加载器的父类加载器【双亲委托机制】。

支持并发类加载的类加载器叫做并行类加载器,要求在初始化期间通过**ClassLoader.registerAsParallelCapable **方法注册自身,ClassLoader类默认被注册为可以并行,但是如果它的子类也是并行加载的话需要单独去注册子类。

在委托模型不是严格的层次化的环境下,类加载器需要并行,否则类加载会导致死锁,因为加载器的锁在类加载过程中是一直被持有的。

通常情况下,Java虚拟机以平台相关的形式从本地的文件系统中加载类,比如在UNIX系统,虚拟机从CLASSPATH环境所定义的目录加载类。

然而,有些类并不是来自于文件;它们是从其它来源得到的,比如网络,或者是由应用本身构建【动态代理】。定义类(defineClass )方法会将字节数组转换为Class的实例,这个新定义类的实例可以由Class.newInstance创建。

由类加载器创建的对象的方法和构造方法可能引用其它的类,为了确定被引用的类,Java虚拟机会调用最初创建类的类加载器的loadClass方法。

二进制名称:以字符串参数的形式向CalssLoader提供的任意一个类名,必须是一个二进制的名称,包含以下四种情况

  • "java.lang.String" 正常类
  • "javax.swing.JSpinner$DefaultEditor" 内部类
  • "java.security.KeyStore\(Builder\)FileBuilder$1" KeyStore的内部类Builder的内部类FileBuilder的第一个匿名内部类
  • "java.net.URLClassLoader$3$1" URLClassLoader类的第三个匿名内部类的第一个匿名内部类

代码一:

public class Test12 {
public static void main(String[] args) {
String[] strings = new String[6];
System.out.println(strings.getClass().getClassLoader());
// 运行结果:null Test12[] test12s = new Test12[1];
System.out.println(test12s.getClass().getClassLoader());
// 运行结果:sun.misc.Launcher$AppClassLoader@18b4aac2 int[] ints = new int[2];
System.out.println(ints.getClass().getClassLoader());
// 运行结果:null
}
}

loadClass方法

loadClass的源码如下, loadClass方法加载拥有指定的二进制名称的Class,默认按照如下顺序寻找类:

  • 调用findLoadedClass(String)检查这个类是否被加载
  • 调用父类加载器的loadClass方法,如果父类加载器为null,就会调用启动类加载器
  • 调用findClass(String)方法寻找

使用上述步骤如果类被找到且resolve为true,就会去调用resolveClass(Class)方法

protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
synchronized (getClassLoadingLock(name)) {
// First, check if the class has already been loaded
Class<?> c = findLoadedClass(name);
if (c == null) {
long t0 = System.nanoTime();
try {
if (parent != null) {
c = parent.loadClass(name, false);
} else {
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
// ClassNotFoundException thrown if class not found
// from the non-null parent class loader
} if (c == null) {
// If still not found, then invoke findClass in order
// to find the class.
long t1 = System.nanoTime();
c = findClass(name); // this is the defining class loader; record the stats
sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
sun.misc.PerfCounter.getFindClasses().increment();
}
}
if (resolve) {
resolveClass(c);
}
return c;
}
}

findClass方法

findClass的源码如下,findClass寻找拥有指定二进制名称的类,JVM鼓励我们重写此方法,需要自定义加载器遵循双亲委托机制,该方法会在检查完父类加载器之后被loadClass方法调用,默认返回ClassNotFoundException异常。

protected Class<?> findClass(String name) throws ClassNotFoundException {
throw new ClassNotFoundException(name);
}

defineClass方法

defineClass的源码如下,defineClass方法将一个字节数组转换为Class的实例。

protected final Class<?> defineClass(String name, byte[] b, int off, int len,
ProtectionDomain protectionDomain)
throws ClassFormatError
{
protectionDomain = preDefineClass(name, protectionDomain);
String source = defineClassSourceLocation(protectionDomain);
Class<?> c = defineClass1(name, b, off, len, protectionDomain, source);
postDefineClass(c, protectionDomain);
return c;
}

自定义类加载器

/**
* 继承了ClassLoader,这是一个自定义的类加载器
* @author 夜的那种黑丶
*/
public class ClassLoaderTest extends ClassLoader {
public static void main(String[] args) throws Exception {
ClassLoaderTest loader = new ClassLoaderTest("loader");
Class<?> clazz = loader.loadClass("classloader.Test01");
Object object = clazz.newInstance();
System.out.println(object);
System.out.println(object.getClass().getClassLoader());
}
//------------------------------以上为测试代码--------------------------------- /**
* 类加载器名称,标识作用
*/
private String classLoaderName; /**
* 从磁盘读物字节码文件的扩展名
*/
private String fileExtension = ".class"; /**
* 创建一个类加载器对象,将系统类加载器当做该类加载器的父加载器
* @param classLoaderName 类加载器名称
*/
private ClassLoaderTest(String classLoaderName) {
// 将系统类加载器当做该类加载器的父加载器
super();
this.classLoaderName = classLoaderName;
} /**
* 创建一个类加载器对象,显示指定该类加载器的父加载器
* 前提是需要有一个类加载器作为父加载器
* @param parent 父加载器
* @param classLoaderName 类加载器名称
*/
private ClassLoaderTest(ClassLoader parent, String classLoaderName) {
// 显示指定该类加载器的父加载器
super(parent);
this.classLoaderName = classLoaderName;
} /**
* 寻找拥有指定二进制名称的类,重写ClassLoader类的同名方法,需要自定义加载器遵循双亲委托机制
* 该方法会在检查完父类加载器之后被loadClass方法调用
* 默认返回ClassNotFoundException异常
* @param className 类名
* @return Class的实例
* @throws ClassNotFoundException 如果类不能被找到,抛出此异常
*/
@Override
protected Class<?> findClass(String className) throws ClassNotFoundException {
byte[] data = this.loadClassData(className);
/*
* 通过defineClass方法将字节数组转换为Class
* defineClass:将一个字节数组转换为Class的实例,在使用这个Class之前必须要被解析
*/
return this.defineClass(className, data, 0 , data.length);
} /**
* io操作,根据类名找到对应文件,返回class文件的二进制信息
* @param className 类名
* @return class文件的二进制信息
* @throws ClassNotFoundException 如果类不能被找到,抛出此异常
*/
private byte[] loadClassData(String className) throws ClassNotFoundException {
InputStream inputStream = null;
byte[] data;
ByteArrayOutputStream byteArrayOutputStream = null; try {
this.classLoaderName = this.classLoaderName.replace(".", "/");
inputStream = new FileInputStream(new File(className + this.fileExtension));
byteArrayOutputStream = new ByteArrayOutputStream(); int ch;
while (-1 != (ch = inputStream.read())) {
byteArrayOutputStream.write(ch);
} data = byteArrayOutputStream.toByteArray();
} catch (Exception e) {
throw new ClassNotFoundException();
} finally {
try {
if (inputStream != null) {
inputStream.close();
}
if (byteArrayOutputStream != null) {
byteArrayOutputStream.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
return data;
}
}

以上是一段自定义类加载器的代码,我们执行这段代码

classloader.Test01@7f31245a
sun.misc.Launcher$AppClassLoader@18b4aac2

可以看见,这段代码中进行类加载的类加载器还是系统类加载器(AppClassLoader)。这是因为jvm的双亲委托机制造成的,private ClassLoaderTest(String classLoaderName)将系统类加载器当做我们自定义类加载器的父加载器,jvm的双亲委托机制使自定义类加载器委托系统类加载器完成加载。

改造以下代码,添加一个path属性用来指定类加载位置:

public class ClassLoaderTest extends ClassLoader {
public static void main(String[] args) throws Exception {
ClassLoaderTest loader = new ClassLoaderTest("loader");
loader.setPath("/home/fanxuan/Study/java/jvmStudy/out/production/jvmStudy/");
Class<?> clazz = loader.loadClass("classloader.Test01");
System.out.println("class:" + clazz); Object object = clazz.newInstance();
System.out.println(object);
System.out.println(object.getClass().getClassLoader());
}
//------------------------------以上为测试代码--------------------------------- /**
* 从指定路径加载
*/
private String path; ...... /**
* io操作,根据类名找到对应文件,返回class文件的二进制信息
* @param className 类名
* @return class文件的二进制信息
* @throws ClassNotFoundException 如果类不能被找到,抛出此异常
*/
private byte[] loadClassData(String className) throws ClassNotFoundException {
InputStream inputStream = null;
byte[] data;
ByteArrayOutputStream byteArrayOutputStream = null; className = className.replace(".", "/"); try {
this.classLoaderName = this.classLoaderName.replace(".", "/");
inputStream = new FileInputStream(new File(this.path + className + this.fileExtension));
byteArrayOutputStream = new ByteArrayOutputStream(); int ch;
while (-1 != (ch = inputStream.read())) {
byteArrayOutputStream.write(ch);
} data = byteArrayOutputStream.toByteArray();
} catch (Exception e) {
throw new ClassNotFoundException();
} finally {
try {
if (inputStream != null) {
inputStream.close();
}
if (byteArrayOutputStream != null) {
byteArrayOutputStream.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
return data;
} public void setPath(String path) {
this.path = path;
}
}

运行一下

class:class classloader.Test01
classloader.Test01@7f31245a
sun.misc.Launcher$AppClassLoader@18b4aac2

修改一下测试代码,并删除工程下的Test01.class文件

public static void main(String[] args) throws Exception {
ClassLoaderTest loader = new ClassLoaderTest("loader");
loader.setPath("/home/fanxuan/桌面/");
Class<?> clazz = loader.loadClass("classloader.Test01");
System.out.println("class:" + clazz); Object object = clazz.newInstance();
System.out.println(object);
System.out.println(object.getClass().getClassLoader());
}

运行一下

class:class classloader.Test01
classloader.Test01@135fbaa4
classloader.ClassLoaderTest@7f31245a

分析

改造后的两块代码,第一块代码中加载类的是系统类加载器AppClassLoader,第二块代码中加载类的是自定义类加载器ClassLoaderTest。是因为ClassLoaderTest会委托他的父加载器AppClassLoader加载class,第一块代码的path直接是工程下,AppClassLoader可以加载到,而第二块代码的path在桌面目录下,所以AppClassLoader无法加载到,然后ClassLoaderTest自身尝试加载并成功加载到。如果第二块代码工程目录下的Test01.class文件没有被删除,那么依然是AppClassLoader加载。

再来测试一块代码

public static void main(String[] args) throws Exception {
ClassLoaderTest loader = new ClassLoaderTest("loader");
loader.setPath("/home/fanxuan/Study/java/jvmStudy/out/production/jvmStudy/");
Class<?> clazz = loader.loadClass("classloader.Test01");
System.out.println("class:" + clazz.hashCode()); Object object = clazz.newInstance();
System.out.println(object.getClass().getClassLoader()); ClassLoaderTest loader2 = new ClassLoaderTest("loader");
loader2.setPath("/home/fanxuan/Study/java/jvmStudy/out/production/jvmStudy/");
Class<?> clazz2 = loader2.loadClass("classloader.Test01");
System.out.println("class:" + clazz2.hashCode()); Object object2 = clazz2.newInstance();
System.out.println(object2.getClass().getClassLoader());
}

结果显而易见,类由系统类加载器加载,并且clazz和clazz2是相同的。

class:2133927002
sun.misc.Launcher$AppClassLoader@18b4aac2
class:2133927002
sun.misc.Launcher$AppClassLoader@18b4aac2

在改造一下

public static void main(String[] args) throws Exception {
ClassLoaderTest loader = new ClassLoaderTest("loader");
loader.setPath("/home/fanxuan/桌面/");
Class<?> clazz = loader.loadClass("classloader.Test01");
System.out.println("class:" + clazz.hashCode()); Object object = clazz.newInstance();
System.out.println(object.getClass().getClassLoader()); ClassLoaderTest loader2 = new ClassLoaderTest("loader2");
loader2.setPath("/home/fanxuan/桌面/");
Class<?> clazz2 = loader2.loadClass("classloader.Test01");
System.out.println("class:" + clazz2.hashCode()); Object object2 = clazz2.newInstance();
System.out.println(object2.getClass().getClassLoader());
}

运行结果

class:325040804
classloader.ClassLoaderTest@7f31245a
class:621009875
classloader.ClassLoaderTest@45ee12a7

ClassLoaderTest是显而易见,但是clazz和clazz2是不同的,这是因为类加载器的命名空间的原因。

我们可以通过设置父类加载器来让loader和loader2处于同一命名空间

public static void main(String[] args) throws Exception {
ClassLoaderTest loader = new ClassLoaderTest("loader");
loader.setPath("/home/fanxuan/桌面/");
Class<?> clazz = loader.loadClass("classloader.Test01");
System.out.println("class:" + clazz.hashCode()); Object object = clazz.newInstance();
System.out.println(object.getClass().getClassLoader()); ClassLoaderTest loader2 = new ClassLoaderTest(loader, "loader2");
loader2.setPath("/home/fanxuan/桌面/");
Class<?> clazz2 = loader2.loadClass("classloader.Test01");
System.out.println("class:" + clazz2.hashCode()); Object object2 = clazz2.newInstance();
System.out.println(object2.getClass().getClassLoader());
}

运行结果

class:325040804
classloader.ClassLoaderTest@7f31245a
class:325040804
classloader.ClassLoaderTest@7f31245a

扩展:命名空间

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

类加载器 - ClassLoader详解的更多相关文章

  1. 类加载器 ClassLoder详解

    一.类加载器定义 从作用的角度来认识类加载器:(根据API文档) 1.类加载器是一个负责加载类的对象,而ClassLoader是一个抽象类.类加载器将xxx.class文件加载到JVM,生成xxx的C ...

  2. jvm之java类加载机制和类加载器(ClassLoader),方法区结构,堆中实例对象结构的详解

    一.类加载或类初始化:当程序主动使用某个类时,如果该类还未被加载到内存中,则JVM会通过加载.连接.初始化3个步骤来对该类进行初始化.如果没有意外,JVM将会连续完成3个步骤. 二.类加载时机:  1 ...

  3. Java ClassLoader详解(转载)

    Java ClassLoader详解 类加载器是 Java 语言的一个创新,也是 Java 语言流行的重要原因之一.它使得 Java 类可以被动态加载到 Java 虚拟机中并执行.类加载器从 JDK ...

  4. Java虚拟机学习(5):类加载器(ClassLoader

    类加载器 类加载器(ClassLoader)用来加载 class字节码到 Java 虚拟机中.一般来说,Java 虚拟机使用 Java 类的方式如下:Java 源文件在经过 Javac之后就被转换成 ...

  5. 深入理解Java类加载器(ClassLoader)

    深入理解Java类加载器(ClassLoader) Java学习记录--委派模型与类加载器 关于Java类加载双亲委派机制的思考(附一道面试题) 真正理解线程上下文类加载器(多案例分析) [jvm解析 ...

  6. 系统批量运维管理器Fabric详解

    系统批量运维管理器Fabric详解 Fabrici 是基于python现实的SSH命令行工具,简化了SSH的应用程序部署及系统管理任务,它提供了系统基础的操作组件,可以实现本地或远程shell命令,包 ...

  7. GridBagLayout布局管理器应用详解

    http://www.cnblogs.com/kungfupanda/p/7220217.html GridBagLayout布局管理器应用详解 很多情况下,我们已经不需要通过编写代码来实现一个应用程 ...

  8. 深入理解Java类加载器(ClassLoader) (转)

    转自: http://blog.csdn.net/javazejian/article/details/73413292 关联文章: 深入理解Java类型信息(Class对象)与反射机制 深入理解Ja ...

  9. java8学习之收集器用法详解与多级分组和分区

    收集器用法详解: 在上次已经系统的阅读了Collector收集器的Javadoc对它已经有一个比较详细的认知了,但是!!!它毕境是只是一个接口,要使用的话还得用它的实现类,所以在Java8中有它进行了 ...

随机推荐

  1. Ubuntu 16.04 集成安装Apache+PHP+Kerberos+LDAP+phpLDAPadmin

    一.安装Apache 1.1.安装Apache apt-get update apt-get install apache2 过程如下: root@duke01:~# apt-get update命中 ...

  2. Mysql数据类型最细讲解

    文章原创于公众号:程序猿周先森.本平台不定时更新,喜欢我的文章,欢迎关注我的微信公众号. 数据库中事务是最重要的概念之一,所以上篇着重谈了谈数据库中事务的使用,并且举了实例如何在实际开发中去使用事务进 ...

  3. LWIP移植文件介绍

    在介绍文件之前首先介绍一下DMA描述符 stm32以太网模块接收/发送FIFO和内存之间的以太网传输是通过以太网DMA使用DMA描述符完成的,一共有两个描述符列表:一个用于接收,一个用于发送, 两个列 ...

  4. 【OUC2019写作】学术论文写作第九小组第一次博客作业

    个人简介 潘旻琦:我是潘旻琦:我的爱好是游泳:羊肉泡馍是海大食堂中我最喜欢的一道菜(清真食堂):一句想说的话是:“追随本心,坚持不懈”. 郭念帆:我是郭念帆:我的爱好是足球:海大食堂中最喜欢的一道菜偏 ...

  5. ios 键盘弹起bug,出现的问题,光标穿透,页面无法点击

    有时候使用ios输入键盘以后,直接点击页面按钮会出现事件无效. 解决方法: 1. 输入框输入后点击提交按钮后,弹窗会发现光标穿透问题 解决方法: 使用input blur()事件使input失去焦点 ...

  6. 在npm上发布自己的vue组件库(使用npm install 或者 CDN的方式引用)

    一.npm publish发布包到npm库的命令是npm publish npm publish发布包,需要先配置webpack.json文件,如果没有webpack.json文件,可以通过npm i ...

  7. 10 python学习笔记-操作数据库(十)

    在功能.接口测试中,常常需要通过数据库的操作,来准备数据.检测环境及核对功能.接口的数据库操作是否正确. 在自动化测试中,就需要我们用代码连接数据库自动完成数据准备. 环境检查及数据库断言的功能.数据 ...

  8. R的安装

    更新时间:2019.09.23 1. 序言 之前曾经用过一段时间的R(一直忍受着原生R那个超级"简洁"的界面),但是后来重装了系统并且学习了Python,就没有再怎么碰过R了.然而 ...

  9. Java初学者的学习路线推荐

    Java学习这一部分其实也算是今天的重点,这一部分用来回答很多群里的朋友所问过的问题,那就是你是如何学习Java的,能不能给点建议?今天我是打算来点干货,因此咱们就不说一些学习方法和技巧了,直接来谈每 ...

  10. HyperLPR车牌识别

    简介 本文基于HyperLPR进行修改,完整代码参考https://github.com/Liuyubao/PlateRecognition. HyperLPR是一个使用深度学习针对对中文车牌识别的实 ...