前言

前面介绍类加载器的时候,介绍了一下命名空间这个概念。今天就通过一个例子,来详细了解一下【类加载器的命名空间】。然后通过这个例子,我们可以总结一下双亲委托模型的好处与优点。

例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点共同决定:

  1. 该类的完全限定名(binary name)。
  2. 用于加载该类的[定义类加载器],即defining class loader。

    上述2点都一样,才代表该类(可以理解为该类的Class对象)是一样的。

    如果同样的名字,不同的类加载器加载,那么这2个类是不一样的。即使.class文件完全一样,.class文件路径一样,这2个类也是不一样的。

双亲委托模型的好处

  1. 确保Java核心类库的安全:所有的Java应用都至少会引用java.lang.Object类,也就是说在运行期,java.lang.Object类会被加载到Java虚拟机当中;如果这个加载过程是由Java应用自己的类加载器所完成的,那么可能会在JVM中存在多个版本的java.lang.Object类,而且这些类还是不兼容的、相互不可见的(因为命名空间的原因)。借助双亲委托机制,Java核心类库中的类的加载工作都是由启动类加载器来统一完成的,从而确保了Java应用所使用的都是同一个版本的Java核心类库,他们之间是互相兼容的。
  2. 确保Java核心类库提供的类不会被自定义的类所替代。
  3. 不同的类加载器可以为相同名称(binary name)的类创建额外的命名空间。相同名称的类可以并存在Java虚拟机中,只需要用不同的类加载器来加他们即可,不同类加载器所加载的类是不兼容的,这就相当于在Java虚拟机内部创建了一个又一个相互隔离的Java类空间。

【Java虚拟机9】类加载器之命名空间详解的更多相关文章

  1. Java 虚拟机运行时数据区详解

    本文摘自深入理解 Java 虚拟机第三版 概述 Java 虚拟机在执行 Java 程序的过程中会把它所管理的内存划分为若干个不同的数据区域,这些区域有各自的用途,以及创建和销毁的时间,有的区域随着虚拟 ...

  2. 深入理解Java虚拟机(四)——HotSpot垃圾收集器详解

    垃圾收集器 新生代收集器 1.Serial收集器 特点: 单线程工作,收集的时候就会停止其他所有工作线程,用户不可知不可控,会使得用户界面出现停顿. 简单高效,是所有收集器中额外内存消耗最少的. 没有 ...

  3. 【java虚拟机】垃圾回收机制详解

    作者:平凡希 原文地址:https://www.cnblogs.com/xiaoxi/p/6486852.html 一.为什么需要垃圾回收 如果不进行垃圾回收,内存迟早都会被消耗空,因为我们在不断的分 ...

  4. Java 容器之Hashset 详解

    Java 容器之Hashset 详解.http://blog.csdn.net/nvd11/article/details/27716511

  5. Java 反射 设计模式 动态代理机制详解 [ 转载 ]

    Java 反射 设计模式 动态代理机制详解 [ 转载 ] @author 亦山 原文链接:http://blog.csdn.net/luanlouis/article/details/24589193 ...

  6. 牛客网 Java 工程师能力评估 20 题 - 详解

    牛客网 Java 工程师能力评估 20 题 - 详解 不知在看博客的你是否知道 牛客网,不知道就太落后了,分享给你 : 牛客网 此 20 题,绝对不只是 20 题! 免责声明:本博客为学习笔记,如有侵 ...

  7. Java线程创建形式 Thread构造详解 多线程中篇(五)

    Thread作为线程的抽象,Thread的实例用于描述线程,对线程的操纵,就是对Thread实例对象的管理与控制. 创建一个线程这个问题,也就转换为如何构造一个正确的Thread对象. 构造方法列表 ...

  8. Android为TV端助力 转载:Android绘图Canvas十八般武器之Shader详解及实战篇(上)

    前言 Android中绘图离不开的就是Canvas了,Canvas是一个庞大的知识体系,有Java层的,也有jni层深入到Framework.Canvas有许多的知识内容,构建了一个武器库一般,所谓十 ...

  9. Java性能分析之线程栈详解与性能分析

    Java性能分析之线程栈详解 Java性能分析迈不过去的一个关键点是线程栈,新的性能班级也讲到了JVM这一块,所以本篇文章对线程栈进行基础知识普及以及如何对线程栈进行性能分析. 基本概念 线程堆栈也称 ...

随机推荐

  1. ElasticAlert基于聚合告警

    背景 最近公司网站经常被漏洞扫描,虽然并没有什么漏洞给对方利用,但是每次扫描我们也必须要察觉到,如果扫描的量太大,可以考虑从公有云的安全组上禁用掉这个IP,所以需要统计指定时间内每个IP的访问次数,这 ...

  2. Mybatis-Plus常用的查询方法--看这一篇就够了!!!

    前言: Mybatis-Plus作为Mybatis的增强,自己封装了很多简单还用的方法,来解脱自己写sql! 对于项目的搭建小编就不在说了,可以参考: SpringBoot+Mybatis-Plus的 ...

  3. Android线程池使用介绍

    本文主要使用kotlin,讨论Android开发中的线程池用法. 我们想使用线程的时候,可以直接创建子线程并启动 Thread { Log.d("rfDev", "rus ...

  4. 洛谷P1582——倒水(进制,数学)

    https://www.luogu.org/problem/show?pid=1582 题目描述 一天,CC买了N个容量可以认为是无限大的瓶子,开始时每个瓶子里有1升水.接着~~CC发现瓶子实在太多了 ...

  5. 学习PDO中的错误与错误处理模式

    在 PDO 的学习过程中,我们经常会在使用事务的时候加上 try...catch 来进行事务的回滚操作,但是大家有没有注意到默认情况下 PDO 是如何处理错误语句导致的数据库操作失败问题呢?今天,我们 ...

  6. Mysql实现无插入有更新(不知主键的情况下)

    网上很多资料说有两种方式 (必须现有唯一键) 1.INSERT 中ON DUPLICATE KEY UPDATE的使用 2.REPLACE的使用 通过可以得出结果: 如果a和b字段 能决定唯一 例子: ...

  7. Linux系列(41) - 监听命令Vmstart,Top(还需完善)

    一.简介 vmstat是Virtual Meomory Statistics(虚拟内存统计)的缩写,可对操作系统的虚拟内存.进程.CPU活动进行监控:属于sysstat包:它是对系统的整体情况进行统计 ...

  8. Linux系列(33)- rpm命令管理之RPM包校验提取(5)

    校验 格式 rpm -V 已安装的包名 选项: - -V:校验指定RPM包中的文件(verify) 例子 rpm -V httpd 后, 无任何提示, 代表该文件没有被做任何修改 # 判断本地的apa ...

  9. Linux 清理缓存

    1. free -m 命令可以查看内存使用情况  2. sync   :因为系统在操作的过程当中,会把你的操作到的文件资料先保存到buffer中去,因为怕你在操作的过程中因为断电等原因遗失数据,所以在 ...

  10. cordova 打包 守护进程无法启动

    方案 1 : 添加环境变量 _JAVA_OPTIONS = -Xmx512M 2: 在新建的 系统变量里 变量名   _JAVA_OPTIONS 变量值   -Djava.net.preferIPv4 ...