【Java虚拟机9】类加载器之命名空间详解
前言
前面介绍类加载器的时候,介绍了一下命名空间这个概念。今天就通过一个例子,来详细了解一下【类加载器的命名空间】。然后通过这个例子,我们可以总结一下双亲委托模型的好处与优点。
例1(不删除classpath下的class文件)
首先定义一个MyPerson
package com.jamie.jvmstudy;
public class MyPerson {
private MyPerson myPerson;
public void setMyPerson(Object obj){
this.myPerson = (MyPerson)obj;
}
}
然后是自定义类加载器
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();
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("findClass invoked : " + className);
System.out.println("class loader name : " + this.classLoaderName);
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(this.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 void setPath(String path) {
this.path = path;
}
}
测试客户端类
package com.jamie.jvmstudy;
import java.lang.reflect.Method;
public class TestClassLoaderNameSpace {
public static void main(String[] args) throws Exception {
CustomizedClassLoader loader1 = new CustomizedClassLoader("loader1");
CustomizedClassLoader loader2 = new CustomizedClassLoader("loader2");
Class<?> clazz1 = loader1.loadClass("com.jamie.jvmstudy.MyPerson");
Class<?> clazz2 = loader2.loadClass("com.jamie.jvmstudy.MyPerson");
System.out.println("clazz1的classLoader是" + clazz1.getClassLoader());
System.out.println("clazz2的classLoader是" + clazz2.getClassLoader());
System.out.println( clazz1 == clazz2);
Object object1 = clazz1.newInstance();
Object object2 = clazz2.newInstance();
Method method = clazz1.getMethod("setMyPerson", Object.class);
method.invoke(object1, object2);
}
}
结果:
clazz1的classLoader是sun.misc.Launcher$AppClassLoader@14dad5dc
clazz2的classLoader是sun.misc.Launcher$AppClassLoader@14dad5dc
true
说明:
同一个类加载器(本例是应用类加载器)加载同一个类,得到的class对象是相同的。
例2(基于例1修改,删除classpath下的class文件)
操作
为自定义类加载器设置path,然后编译成功后,删除掉classpath下面的MyPerson.class文件,把编译出的MyPerson.class文件移动到D:/temp文件夹里面。
public class TestClassLoaderNameSpace {
public static void main(String[] args) throws Exception {
CustomizedClassLoader loader1 = new CustomizedClassLoader("loader1");
CustomizedClassLoader loader2 = new CustomizedClassLoader("loader2");
loader1.setPath("D:/temp/");
loader2.setPath("D:/temp/");
Class<?> clazz1 = loader1.loadClass("com.jamie.jvmstudy.MyPerson");
Class<?> clazz2 = loader2.loadClass("com.jamie.jvmstudy.MyPerson");
System.out.println("clazz1的classLoader是" + clazz1.getClassLoader());
System.out.println("clazz2的classLoader是" + clazz2.getClassLoader());
System.out.println( clazz1 == clazz2);
Object object1 = clazz1.newInstance();
Object object2 = clazz2.newInstance();
Method method = clazz1.getMethod("setMyPerson", Object.class);
method.invoke(object1, object2);
}
}
代码执行结果
findClass invoked : com.jamie.jvmstudy.MyPerson
class loader name : loader1
findClass invoked : com.jamie.jvmstudy.MyPerson
class loader name : loader2
clazz1的classLoader是com.jamie.jvmstudy.CustomizedClassLoader@677327b6
clazz2的classLoader是com.jamie.jvmstudy.CustomizedClassLoader@7f31245a
false
Exception in thread "main" java.lang.reflect.InvocationTargetException
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:497)
at com.intellij.rt.execution.CommandLineWrapper.main(CommandLineWrapper.java:67)
Caused by: java.lang.reflect.InvocationTargetException
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:497)
at com.jamie.jvmstudy.TestClassLoaderNameSpace.main(TestClassLoaderNameSpace.java:23)
... 5 more
Caused by: java.lang.ClassCastException: com.jamie.jvmstudy.MyPerson cannot be cast to com.jamie.jvmstudy.MyPerson
at com.jamie.jvmstudy.MyPerson.setMyPerson(MyPerson.java:8)
... 10 more
结论
loader1和loader2分别加载了MyPerson.class,分别给MyPerson.class分配了内存空间,如下图:

这2个class对象虽然在文件系统是来自于同一个class文件,但是由于他们是被自定义类加载器加载的,并且这2个自定义类加载器是同级的,没有父子关系。所以双亲委派模型中,他们是看不到对方的命名空间的。
所以clazz1 == clazz2的结果为false
自己画了一个简图:

下面这张是偶尔在网上看到的:

不同类加载器的命名空间关系
咱们先回顾一下命名空间的概念:
- 每个类加载器都有自己的命名空间。命名空间由该加载器和所有父加载器所加载的类组成。(请结合下图一起看,想明白)
- 在同一个命名空间中,不会出现类的完整名字(包括类的包名)相同的两个类。
- 在不同的命名空间中,有可能会出现类的完整名字(包括类的包名)相同的两个类。
得出命名空间的关系如下:(请结合下图一起看,想明白)
- 同一个命名空间的类是相互可见的。
- 子加载器的命名空间包含所有父加载器的命名空间。因此由子加载器加载的类能看见父加载器加载的类。例如系统类加载器能看见根类加载器加载的类。
- 由父类加载器加载的类不能看见子加载器加载的类。
- 如果两个加载器没有父子关系,那么他们自己加载的类互相不可见。

类的唯一性
在运行期,一个类的唯一性是由以下2点共同决定:
- 该类的完全限定名(binary name)。
- 用于加载该类的[定义类加载器],即defining class loader。
上述2点都一样,才代表该类(可以理解为该类的Class对象)是一样的。
如果同样的名字,不同的类加载器加载,那么这2个类是不一样的。即使.class文件完全一样,.class文件路径一样,这2个类也是不一样的。
双亲委托模型的好处
- 确保Java核心类库的安全:所有的Java应用都至少会引用java.lang.Object类,也就是说在运行期,java.lang.Object类会被加载到Java虚拟机当中;如果这个加载过程是由Java应用自己的类加载器所完成的,那么可能会在JVM中存在多个版本的java.lang.Object类,而且这些类还是不兼容的、相互不可见的(因为命名空间的原因)。借助双亲委托机制,Java核心类库中的类的加载工作都是由启动类加载器来统一完成的,从而确保了Java应用所使用的都是同一个版本的Java核心类库,他们之间是互相兼容的。
- 确保Java核心类库提供的类不会被自定义的类所替代。
- 不同的类加载器可以为相同名称(binary name)的类创建额外的命名空间。相同名称的类可以并存在Java虚拟机中,只需要用不同的类加载器来加他们即可,不同类加载器所加载的类是不兼容的,这就相当于在Java虚拟机内部创建了一个又一个相互隔离的Java类空间。
【Java虚拟机9】类加载器之命名空间详解的更多相关文章
- Java 虚拟机运行时数据区详解
本文摘自深入理解 Java 虚拟机第三版 概述 Java 虚拟机在执行 Java 程序的过程中会把它所管理的内存划分为若干个不同的数据区域,这些区域有各自的用途,以及创建和销毁的时间,有的区域随着虚拟 ...
- 深入理解Java虚拟机(四)——HotSpot垃圾收集器详解
垃圾收集器 新生代收集器 1.Serial收集器 特点: 单线程工作,收集的时候就会停止其他所有工作线程,用户不可知不可控,会使得用户界面出现停顿. 简单高效,是所有收集器中额外内存消耗最少的. 没有 ...
- 【java虚拟机】垃圾回收机制详解
作者:平凡希 原文地址:https://www.cnblogs.com/xiaoxi/p/6486852.html 一.为什么需要垃圾回收 如果不进行垃圾回收,内存迟早都会被消耗空,因为我们在不断的分 ...
- Java 容器之Hashset 详解
Java 容器之Hashset 详解.http://blog.csdn.net/nvd11/article/details/27716511
- Java 反射 设计模式 动态代理机制详解 [ 转载 ]
Java 反射 设计模式 动态代理机制详解 [ 转载 ] @author 亦山 原文链接:http://blog.csdn.net/luanlouis/article/details/24589193 ...
- 牛客网 Java 工程师能力评估 20 题 - 详解
牛客网 Java 工程师能力评估 20 题 - 详解 不知在看博客的你是否知道 牛客网,不知道就太落后了,分享给你 : 牛客网 此 20 题,绝对不只是 20 题! 免责声明:本博客为学习笔记,如有侵 ...
- Java线程创建形式 Thread构造详解 多线程中篇(五)
Thread作为线程的抽象,Thread的实例用于描述线程,对线程的操纵,就是对Thread实例对象的管理与控制. 创建一个线程这个问题,也就转换为如何构造一个正确的Thread对象. 构造方法列表 ...
- Android为TV端助力 转载:Android绘图Canvas十八般武器之Shader详解及实战篇(上)
前言 Android中绘图离不开的就是Canvas了,Canvas是一个庞大的知识体系,有Java层的,也有jni层深入到Framework.Canvas有许多的知识内容,构建了一个武器库一般,所谓十 ...
- Java性能分析之线程栈详解与性能分析
Java性能分析之线程栈详解 Java性能分析迈不过去的一个关键点是线程栈,新的性能班级也讲到了JVM这一块,所以本篇文章对线程栈进行基础知识普及以及如何对线程栈进行性能分析. 基本概念 线程堆栈也称 ...
随机推荐
- 操作系统IO之零拷贝技术
磁盘可以说是计算机系统最慢的硬件之一,读写速度相差内存 10 倍以上,所以针对优化磁盘的技术非常的多,比如零拷贝.直接 I/O.异步 I/O 等等,这些优化的目的就是为了提高系统的吞吐量,另外操作系统 ...
- client-go实战之四:dynamicClient
欢迎访问我的GitHub https://github.com/zq2599/blog_demos 内容:所有原创文章分类汇总及配套源码,涉及Java.Docker.Kubernetes.DevOPS ...
- Linux基础命令(基于CentOS7)
1.帮助相关命令 man 查看普通命令的帮助 --help 只能查看内置命令 info 查看一个命令的更多信息 type 查看是否为内置命令 2.关机重启 shutdown -h 关机 -r 重启 - ...
- shell循环语句while
格式1: while 条件 do 执行命令 done 格式2: while 条件;do 命令 done 例子: while [ 1 -eq 1 ];do echo "这一步需要先修改/dat ...
- java 线程状态 详解
线程被创建后,有一个生命周期,下图是线程的生命周期详解. java api java.lang.Thread.State 这个枚举中给出了六种线程状态,分别是: 线程状态 导致状态发生条件 NEW(新 ...
- Axis <=1.4 RCE 复现
1.环境搭建 在idea 上新建项目,然后用tomcat运行即可 2.漏洞复现 2.1 freemarker.template.utility.Execute 如果项目里面没有freemarker 就 ...
- Zookeeper Acl权限 超级用户权限 怎么跳过ACL密码/账户验证
Zookeeper的一个节点不知道什么原因无法删除了,查看日志发现是没有权限, 我们之前使用ACL进行Zookeeper节点的权限管理. 可以解决以下三种但不限于以下三种问题: 1.在设置Acl权限时 ...
- Linux没有/var/log/messages日志文件
1.新安装的CentOS8没有/var/log/messages日志文件: 安装rsyslog: dnf install -y rsyslog 或 yum install -y rsys ...
- MapReduce原理深入理解(一)
1.MapReduce概念 1)MapReduce是一种分布式计算模型,由Google提出,主要用于搜索领域,解决海量数据的计算问题. 2)MapReduce是分布式运行的,由两个阶段组成:Map和R ...
- 在PHP中如何为匿名函数指定this?
在之前的文章中,我们已经学习过匿名函数的使用,没有看过的小伙伴可以进入传送门先去了解下闭包匿名函数的用法,传送:还不知道PHP有闭包?那你真OUT了. 关于闭包匿名函数,在JS中有个很典型的问题就是要 ...