深入理解JVM-类加载器深入解析(3)
深入理解JVM-类加载器深入解析(3)
获得ClassLoader的途径
获得当前类的ClassLoader
clazz.getClassLoader()
获得当前线程上下文的ClassLoader
Thread.currentThread().getContextClassLoader()
获得系统的ClassLoader
ClassLoader.getSystemClassLoader()
获得调用者的ClassLoader
DriverManager.getCallerClassLoader()
例子:
public class MyTest13 {
public static void main(String[] args) {
ClassLoader classLoader = ClassLoader.getSystemClassLoader();
System.out.println(classLoader);
while (null != classLoader) {
classLoader = classLoader.getParent();
System.out.println(classLoader);
}
}
}
public class MyTest14 {
public static void main(String[] args) throws IOException {
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
String resourceName = "jvm/classloader/MyTest13.class";
Enumeration<URL> resources = classLoader.getResources(resourceName);
while (resources.hasMoreElements()) {
URL url = resources.nextElement();
System.out.println(url);
}
}
}
数组的类加载器是在运行期间jvm为其创建的,如果该数组是一个引用类型的数组,那么这个数组的类加载器就是加载该引用类型的类的类加载器,如果是原生类型的数组,那么这个数组是没有类加载器
public class MyTest15 {
/**
* String的类加载器是根类加载器,打印出来为null
* null
* --------
* sun.misc.Launcher$AppClassLoader@18b4aac2
* ---------
* 原始类型的数据没有类加载器
* null
* @param args
*/
public static void main(String[] args) {
String[] strings = new String[2];
System.out.println(strings.getClass().getClassLoader());
System.out.println("--------");
MyTest15[] myTest15s = new MyTest15[2];
System.out.println(myTest15s.getClass().getClassLoader());
System.out.println("---------");
int[] ints = new int[2];
System.out.println(ints.getClass().getClassLoader());
}
}
命名空间
- 每个类加载器都有自己的命名空间,命名空间由该加载器及所以附加在其所加载的类组成.
- 在同一个命名空间中,不会出来类的完整名字(包括类的包名)相同的两个类
- 在不同的命名空间中,有可能会出现类的完整名字(包括类的包名)相同的两个类
- 同一个命名空间内的类是相互可见的
- 子加载器的命名矿建包含所有父加载器的命名空间.因此自加载器加载的类能看见附加在其加载的类.例如系统类加载器加载的类能看见根类加载器加载的类
- 由父加载器加载的类不能看见子加载器加载的类
- 如果两个加载器之间没有直接或间接的父子关系,那么它们各自加载的类相互不可见
类的卸载
当MySample类被加载,连接和初始化后,它的生命周期就开始了.当代表MySample类的Class对象不再被引用,即不可触及时,Class对象就会结束生命周期,MySample类在方法区内的数据也会被卸载,从而结束Sample类的生命周期.
一个类何时结束生命周期,取决于代表它的Class对象何时结束生命周期
由java虚拟机自带的类加载器所加载的类,在虚拟机的生命周期中,始终不会被卸载.
由用户自定义的类加载器所加载的类是可以被卸载的
public class MyTest16 extends ClassLoader{
private final String fileExtension = ".class";
private String classLoadName;
private String path;
public MyTest16(String classLoadName) {
super();
this.classLoadName = classLoadName;
}
public MyTest16(ClassLoader parent,String classLoadName) {
super(parent);
this.classLoadName = classLoadName;
}
public void setPath(String path) {
this.path = path;
}
@Override
protected Class<?> findClass(String className) {
System.out.println("findClass invoked: " + className);
System.out.println("class loader name: " + this.classLoadName);
byte[] data = this.loadClassData(className);
return this.defineClass(className, data, 0, data.length);
}
private byte[] loadClassData(String name) {
InputStream is = null;
byte[] data = null;
ByteArrayOutputStream baos = null;
name = name.replace(".", "/");
try {
is = new FileInputStream(new File(this.path+name + this.fileExtension));
baos = new ByteArrayOutputStream();
int ch = 0;
while (-1 != (ch = is.read())) {
baos.write(ch);
}
data = baos.toByteArray();
} catch (Exception e) {
e.printStackTrace();
}finally {
try {
is.close();
baos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
return data;
}
public static void main(String[] args) throws IllegalAccessException, InstantiationException,
ClassNotFoundException, InterruptedException {
/**
* findClass invoked: jvm.classloader.Mytest1
* class loader name: loader1
* jvm.classloader.Mytest1@60e53b93
*
* [Unloading class jvm.classloader.Mytest1 0x00000007c0061028]
* findClass invoked: jvm.classloader.Mytest1
* class loader name: loader1
* class: 644117698
* jvm.classloader.Mytest1@6f94fa3e
*
* 我们在运行的时候加上-XX:+TraceClassUnloading 这个参数
* 我们在gc之后就会打印出class卸载的信息
*/
MyTest16 loader1 = new MyTest16("loader1");
loader1.setPath("/Users/luozhiyun/Downloads/test/");
Class<?> clazz = loader1.loadClass("jvm.classloader.Mytest1");
Object object = clazz.newInstance();
System.out.println(object);
System.out.println();
loader1 = null;
clazz = null;
object = null;
System.gc();
Thread.sleep(10000);
loader1 = new MyTest16("loader1");
loader1.setPath("/Users/luozhiyun/Downloads/test/");
clazz = loader1.loadClass("jvm.classloader.Mytest1");
System.out.println("class: " + clazz.hashCode());
object = clazz.newInstance();
System.out.println(object);
System.out.println();
/**
* findClass invoked: jvm.classloader.Mytest1
* class loader name: loader1
* jvm.classloader.Mytest1@60e53b93
* findClass invoked: jvm.classloader.Mytest1
* class loader name: loader1
* jvm.classloader.Mytest1@266474c2
* 打印出来的两个加载出来的类的实例都不一样
*/
//MyTest16 myTest16 = new MyTest16("loader1");
//
//myTest16.setPath("/Users/luozhiyun/Downloads/test/");
//
//Class<?> clazz = myTest16.loadClass("jvm.classloader.Mytest1");
//
//Object object = clazz.newInstance();
//
//System.out.println(object);
//
//
//MyTest16 loader = new MyTest16("loader1");
//
//loader.setPath("/Users/luozhiyun/Downloads/test/");
//
//Class<?> clazz2 = loader.loadClass("jvm.classloader.Mytest1");
//
//Object object2 = clazz2.newInstance();
//
//System.out.println(object2);
}
}
public class MySample {
public MySample() {
System.out.println("MySample: " + MyCat.class.getClassLoader());
new MyCat();
}
}
public class MyCat {
public MyCat() {
System.out.println("mycat: " + MyCat.class.getClassLoader());
}
}
public class MyTest17 {
public static void main(String[] args) throws Exception {
MyTest16 loader1 = new MyTest16("loader1");
Class<?> clazz = loader1.loadClass("jvm.classloader.MySample");
System.out.println("class: " + clazz.hashCode());
//如果注释掉该行,那么并不会实例化MySample对象,即MySample构造方法不会被调用
// 因此不会实例化MyCat对象,即没有对MyCat进行主动使用,这里就不会加载MyCat cLass
//Object o = clazz.newInstance();
}
}
public class MyTest18 {
public static void main(String[] args) {
System.out.println(System.getProperty("sun.boot.class.path"));
System.out.println(System.getProperty("java.ext.dirs"));
System.out.println(System.getProperty("java.class.path"));
}
}
public class MyTest18_1 {
public static void main(String[] args) throws Exception {
//Library/Java/JavaVirtualMachines/jdk1.8.0_171.jdk/Contents/Home/jre/classes
//如果我们把自己的class放置到上面的根类加载器加载class的目录下的话, 那么会打印出class loader为null
//也就是说我们的class直接由根类加载器进行加载,不会再由系统加载器加载
//由这个例子我们就可以看出,当前类加载器会先委托父类加载器去加载,
// 如果父类加载器已经加载了,那么当前类加载器是不会再去加载的
MyTest16 loader1 = new MyTest16("loader1");
loader1.setPath("/Users/luozhiyun/Downloads/test/");
Class<?> clazz = loader1.loadClass("jvm.classloader.Mytest1");
System.out.println("class:" + clazz.hashCode());
System.out.println("class loader:" + clazz.getClassLoader());
}
}
public class MyTest19 {
public static void main(String[] args) {
/**
* 使用java -Djava.ext.dirs=./ jvm.classloader.MyTest19
* 会打印出找不到AESKeyGenerator这个类,因为这个类不在当前目录下
*/
AESKeyGenerator aesKeyGenerator = new AESKeyGenerator();
System.out.println(aesKeyGenerator.getClass().getClassLoader());
System.out.println(MyTest19.class.getClassLoader());
}
}
public class MyTest20 {
public static void main(String[] args) throws Exception {
MyTest16 loader1 = new MyTest16("loader1");
MyTest16 loader2 = new MyTest16("loader2");
//loader1.setPath("/Users/luozhiyun/Downloads/test/");
Class<?> clazz1 = loader1.loadClass("jvm.classloader.MyPerson");
Class<?> clazz2 = loader2.loadClass("jvm.classloader.MyPerson");
//true
System.out.println(clazz1 == clazz2);
Object o1 = clazz1.newInstance();
Object o2 = clazz2.newInstance();
Method method = clazz1.getMethod("setMyPerson", Object.class);
method.invoke(o1, o2);
}
}
/**
* 类加载器的双亲委托模型的好处:
* 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类空间,这类技术在很多框架中都得到了实际应用
*/
public class MyTest21 {
public static void main(String[] args) throws Exception {
MyTest16 loader1 = new MyTest16("loader1");
MyTest16 loader2 = new MyTest16("loader2");
loader1.setPath("/Users/luozhiyun/Downloads/test/");
loader2.setPath("/Users/luozhiyun/Downloads/test/");
Class<?> clazz1 = loader1.loadClass("jvm.classloader.MyPerson");
Class<?> clazz2 = loader2.loadClass("jvm.classloader.MyPerson");
//false
//因为loader1和loader2两者是没有任何关系的,并且他们都是各自都加载了一次MyPerson
//所以两个类加载器加载的类其实是两个class
//如果两个加载器之间没有直接或间接的父子关系,那么它们各自加载的类相互不可见
System.out.println(clazz1 == clazz2);
Object o1 = clazz1.newInstance();
Object o2 = clazz2.newInstance();
/**
* 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:498)
* at jvm.classloader.MyTest21.main(MyTest21.java:25)
* Caused by: java.lang.ClassCastException: jvm.classloader.MyPerson cannot be cast to jvm.classloader.MyPerson
* at jvm.classloader.MyPerson.setMyPerson(MyPerson.java:11)
* ... 5 more
* 这里会抛出一个这样的异常,因为两个类实际上已经不是同一个类了,所以不能转换
*/
Method method = clazz1.getMethod("setMyPerson", Object.class);
method.invoke(o1, o2);
}
}
类加载器的双亲委托模型的好处:
- 可以确保java核心库的类型安全: 所有的java应用至少会引用java.lang.Object类,也就是说在运行期,java.lang.Object这个类会被加载到java虚拟机中;如果这个加载过程是由java应用自己的类加载器所完成的,那么很可能就会在
Jvm中存在多个版本的java.lang.Object类,而且这些类之间还是不兼容,相互不可见的(正是命名空间在发挥着作用).
借助于双亲委托机制,java核心类库中的类的加载工作都是由启动类加载器来统一完成加载工作,从而确保了java应用所使用的都是同一版本的java核心类库,他们之间是相互兼容的. - 可以确保java核心类库所提供的类不会被自定义的类所替代
- 不同的类加载器可以被相同名称(binary name)的类创建额外的命名空间,相同名称的类可以并存在java虚拟机中,只需要用不同的加载器来加载他们即可,不同类加载器所加载的类之间是不兼容的,这相当于在java虚拟机内部创建了一个又一个相互隔离的java类空间,这类技术在很多框架中都得到了实际应用
在运行期,一个java类是由该类的完全限定名(binary name,二进制名)和用于加载该类的定义类加载器(defining loader)所共同决定的.如果同样名字(即相同的完全限定名)的类是由两个不同的类加载器所加载,那么这些类就是不同的,即便.class文件的字节码完全一样,并且从相同的位置加载亦如此.
在Oracle的Hotspot实现中,系统属性sun.boot.class.path如果修改错了,则运行会出错,提示如下错误信息:
Error occurred during initialization of VM
java/lang/NoClassDefFoundError: java/lang/Object
/*
在运行期,一个java类是由该类的完全限定名(binary name,二进制名)和用于加载该类的定义类加载器(defining loader)所共同决定的.
如果同样名字(即相同的完全限定名)的类是由两个不同的类加载器所加载,那么这些类就是不同的,
即便.class文件的字节码完全一样,并且从相同的位置加载亦如此.
*/
/*
java -Dsun.boot.class.path=./ jvm.classloader.MyTest23
在Oracle的Hotspot实现中,系统属性sun.boot.class.path如果修改错了,则运行会出错,提示如下错误信息:
Error occurred during initialization of VM
java/lang/NoClassDefFoundError: java/lang/Object
*/
public class MyTest23 {
public static void main(String[] args) {
System.out.println(System.getProperty("sun.boot.class.path"));
System.out.println(System.getProperty("java.ext.dirs"));
System.out.println(System.getProperty("java.class.path"));
//null 也就是说由启动类加载器负责加载
System.out.println(ClassLoader.class.getClassLoader());
//扩展类加载器与系统类加载器也是由启动类加载器所加载的
System.out.println(Launcher.class.getClassLoader());
System.out.println("----");
//java -Djava.system.class.loader=jvm.classloader.MyTest16 jvm.classloader.MyTest23
//jvm.classloader.MyTest16
//sun.misc.Launcher$AppClassLoader@18b4aac2
//sun.misc.Launcher$AppClassLoader@18b4aac2
//jvm.classloader.MyTest16@4e25154f
//如果加了上面的那个属性就会这样打印,因为我们把默认的加载器改成了自定义的加载器
System.out.println(System.getProperty("java.system.class.loader"));
System.out.println(MyTest23.class.getClassLoader());
System.out.println(MyTest16.class.getClassLoader());
System.out.println(ClassLoader.getSystemClassLoader());
}
}
内建于jvm中的启动类加载器会加载java.lang.ClassLoader以及其他的java平台类.
当JVM启动时,一块特殊的机器码会运行,它会加载扩展类加载器与系统类加载器(Bootstrap).
启动类加载器并不是java类,而其他的加载器则都是java类
启动类加载器是特定于平台的机器指令,它负责开启整个加载过程,所有类加载器(除了启动类加载器)都被实现为java类.不过,总归要有一个组件来加载第一个java类加载器,从而让整个加载过程能够顺利进行下去,加载第一个纯java类加载器就是启动类加载器的指责.
启动类加载器还会负责加载供jre正常运行所需要的基本组件,这包括java.util与java.lang包中的类等等.
当前类加载器(Current ClassLoader)
每个类都会使用自己的类加载器(即加载自身的类加载器)来去加载其他类(指的是所依赖的类)
如果ClassX引用了ClassY,那么ClasX的类加载器就回去加载ClasY(前提是ClassY尚未被加载)
线程上下文类加载器(Context ClassLoader)
线程上下文类加载器是从JDK1.2开始引入的,类Thread中的与getConTgextClassLoader()与setConTextClassLoader(ClassLoader)
分别用来获取和设置上下文加载器.
如果没有通过setConTextClassLoader进行设值的话,线程将继承其父线程的上下文类加载器.
Java应用运行时的初始线程的上下文类加载器是系统类加载器.在线程中运行的代码可以通过该类加载器来加载类与资源
线程上下文类加载器的重要性:
父classLoader可以使用当前线程Thread.currentThread().getContextLoader()所指定的classLoader加载的类这就改变了父ClassLoader不能使用子ClassLoader或是其他没有直接父子关系的ClassLoader加载类的情况,即改变了双亲委托模型.
线程上下文类加载器就是当前线程的Current ClassLoader.
在双亲委托模型下,类加载时由上至下的,即下层的类加载器会委托上层进行加载.但是对SPI来说,有些接口是java核心库提供的.而java核心库是由启动类加载器来加载的,而这些接口的实现却来自不同的jar包(厂商提供),java的启动类加载器是不会加载其他来源的jar包,这样传统的双亲委托模型就无法满足SPI的要求.而通过给当前线程设置上下文类加载器,就可以由设置的上下文类加载器来实现对接口实现类的加载
线程上下文类加载器的一般使用模式(获取 - 使用 - 还原)
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
try {
将想要使用的类加载器设置进去
Thread.currentThread().setContextClassLoader(targetTccl);
然后在自己写的方法使用类加载器
myMethod();
}finally {
最后把当前线程的类加载器还原
Thread.currentThread().setContextClassLoader(classLoader);
}
如果一个类由类加载器A加载,那么这个类的依赖类也是由相同的类加载器加载的(如果该依赖类之前没有被加载过的话)
ContextClassLoader的作用就输出为了破坏java的类加载器委托机制.
当高层提供了同一的借口让底层去实现,同时又要在高层加载(或实例化)底层的类时,就必须要通过线程上下文类加载器来帮助高层的classLoader找到并加载该类
java学习笔记/jvm#
深入理解JVM-类加载器深入解析(3)的更多相关文章
- JVM 类加载器深入解析以及重要特性剖析
1.类加载流程图 从磁盘加载到销毁的完整过程. 2.类加载流程图2 1.加载: 就是把二进制形式的java类型读入java虚拟机中 2.连接: 验证.准备.解析. 连接就是将已经读入到内存的类的二进制 ...
- 深入理解Java类加载器(一):Java类加载原理解析
摘要: 每个开发人员对java.lang.ClassNotFoundExcetpion这个异常肯定都不陌生,这个异常背后涉及到的是Java技术体系中的类加载机制.本文简述了JVM三种预定义类加载器,即 ...
- 深入理解Java类加载器(ClassLoader)
深入理解Java类加载器(ClassLoader) Java学习记录--委派模型与类加载器 关于Java类加载双亲委派机制的思考(附一道面试题) 真正理解线程上下文类加载器(多案例分析) [jvm解析 ...
- 深入理解Java类加载器(ClassLoader) (转)
转自: http://blog.csdn.net/javazejian/article/details/73413292 关联文章: 深入理解Java类型信息(Class对象)与反射机制 深入理解Ja ...
- 深入理解JVM虚拟机6:深入理解JVM类加载机制
深入理解JVM类加载机制 简述:虚拟机把描述类的数据从class文件加载到内存,并对数据进行校验.转换解析和初始化,最终形成可以被虚拟机直接使用的Java类型,这就是虚拟机的类加载机制. 下面我们具体 ...
- 深入理解Java类加载器(二):线程上下文类加载器
摘要: 博文<深入理解Java类加载器(一):Java类加载原理解析>提到的类加载器的双亲委派模型并不是一个强制性的约束模型,而是Java设计者推荐给开发者的类加载器的实现方式.在Java ...
- JVM类加载器的分类
类加载器的分类 JVM支持两种类型的类加载器,分别为引导类加载器(Bootstrap ClassLoader)和自定义类加载器(User-Defined ClassLoader). 从概念上来讲,自定 ...
- 深入JVM类加载器机制,值得你收藏
先来一道题,试试水平 public static void main(String[] args) { ClassLoader c1 = ClassloaderStudy.class.getClass ...
- 深入理解Java类加载器(1):Java类加载原理解析
1 基本信息 每个开发人员对Java.lang.ClassNotFoundExcetpion这个异常肯定都不陌生,这背后就涉及到了java技术体系中的类加载.Java的类加载机制是技术体系中比较核心的 ...
- JVM 类加载器命名空间深度解析与实例分析
一.创建Sample 1.创建实例 public class MyPerson { private MyPerson myPerson; public void setMyPerson(Object ...
随机推荐
- (持续更新)Qt3D 学习资源
目录 一.前言 1.1 什么是Qt3D 1.2 Qt3D 的利与弊 利:原生支持 弊处:资料过少 二.学习建议 2.1 OpenGL 学习资料 2.2 Qt3D 资料 2.2.1 视频资料 2.2.4 ...
- CentOS7搭建LNMP环境
以前写的过时了,重新发一篇新的. 安装PHP 下载官网:https://www.php.net/downloads.php 为了方便,我存了现成的 百度网盘:https://pan.baidu.com ...
- 随时发布:REST API文档的代码仓库中的持续集成与协作
本文主要内容:API文档提供了预测客户成功的关键路径:在代码附近的文档上进行协作可以更好地检查代码和文档文件,提高自动化效率,并专门针对文档进行质量测试:提供通用文档框架,标准,自动化和工具,以提高团 ...
- C++ 洛谷 P2458 [SDOI2006]保安站岗 from_树形DP
P2458 [SDOI2006]保安站岗 没学树形DP的,看一下. 题目大意:一棵树有N个节点,现在需要将所有节点都看守住,如果我们选择了节点i,那么节点i本身,节点i的父亲和儿子都会被看守住. 每个 ...
- mysql的数据存储
# pycharm 连接mysql import pymysql username = input("输入用户名:") pwd = input("输入密码:") ...
- Linux环境下虚拟环境virtualenv安装和使用(转)
virtualenv用于创建独立的Python环境,多个Python相互独立,互不影响,它能够: 1. 在没有权限的情况下安装新套件 2. 不同应用可以使用不同的套件版本 3. 套件升级不影响其他应用 ...
- web前端css(二)
一. 标准文档流 标准文档流中会有一些现象: 空白折叠 和 高低不齐边底对齐的现象 标准文档流等级森严, 标签分为两种等级: 行内元素 和 块级元素. 1. 行内元素 和 块级元素的区别: 行内元素 ...
- Dom4J的基本使用
初始化数据 <?xml version="1.0" encoding="UTF-8"?> <RESULT> <VALUE> ...
- py+selenium IE 用driver.close()却把两个窗口都关了【已解决】
环境:py3 selenium unittest 测试浏览器:IE10 目标:在单个文件中,有多个用例,执行完A用例,由于打开了新的窗口,必须关闭新的窗口,才不会影响下一条用例的执行. 问题:按例 ...
- Scrum 使用絮叨
关于Scrum 的一些絮叨 Scrum 的推行的基础在于全员参与,全员协作,包含Dev, QA(quality assurance) ,BA(Business analyst)以及Supporter ...