深入理解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;}@Overrideprotected 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");//trueSystem.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);}
}

类加载器的双亲委托模型的好处:

  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类是由该类的完全限定名(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

转载于:https://www.cnblogs.com/luozhiyun/p/10506220.html

深入理解JVM-类加载器深入解析(3)相关推荐

  1. Java高并发编程详解系列-JVM类加载器

    之前的博客中提到了类加载的过程,提到了双亲委托机制,提到了关于类加载器的概念,这篇博客就来给大家分享一下什么是JVM的类加载器.通过实战的方式来了解一下类加载器器到底是什么. JVM类加载器分类    ...

  2. 深入理解JVM虚拟机6:深入理解JVM类加载机制

    深入理解JVM类加载机制 简述:虚拟机把描述类的数据从class文件加载到内存,并对数据进行校验.转换解析和初始化,最终形成可以被虚拟机直接使用的Java类型,这就是虚拟机的类加载机制. 下面我们具体 ...

  3. 深入理解Java类加载器:Java类加载原理解析

    http://blog.csdn.net/zhoudaxia/article/details/35824249 1 基本信息 每个开发人员对java.lang.ClassNotFoundExcetpi ...

  4. JVM类加载器分类和解析

    类加载器 类加载器 类加载器的分类 启动类加载器(引导类加载器,Bootstra ClassLoader) 扩展类加载器(Extension ClassLoader) 用户自定义类加载器 类加载器 类 ...

  5. 《Java虚拟机原理图解》5. JVM类加载器机制与类加载过程

    参考网址:http://blog.csdn.net/luanlouis/article/details/50529868 0.前言 读完本文,你将了解到: 一.为什么说Jabalpur语言是跨平台的 ...

  6. 彻底搞懂JVM类加载器:基本概念

    点击蓝色"程序猿DD"关注我 回复"资源"获取独家整理的学习资料! 作者 | 阿杜的世界 来源 | javaadu 在Java面试中,在考察完项目经验.基础技术 ...

  7. jvm类加载机制_面试:对于JVM类加载机制深度解析

    目录: 前文回顾 JVM在什么情况下会加载一个类? 从实用角度出发,来看看验证.准备和初始化的过程 核心阶段:初始化 类加载器和双亲委派机制 1.前文回顾 咱们今天先来回顾一下昨天讲到的JVM整体的一 ...

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

    1 基本信息 每个开发人员对Java.lang.ClassNotFoundExcetpion这个异常肯定都不陌生,这背后就涉及到了java技术体系中的类加载.Java的类加载机制是技术体系中比较核心的 ...

  9. 22-09-02 西安 JVM 类加载器、栈、堆体系、堆参数调优、GC垃圾判定、垃圾回收算法、对象的finalize机制

    这篇文章不少地方都截图了宋红康老师的课件,实在他jvm这块讲的真好.连接地址如下: 尚硅谷宋红康JVM全套教程(详解java虚拟机)_哔哩哔哩_bilibili JVM入门 1.JVM结构图 JVM是 ...

  10. java类加载器可以从互联网加载,Java类加载器深入解析(2)

    在做Java开发时了解Java类加载机制是非常好的.而对类加载机制的基本理解对Java开发人员处理类加载器(ClassLoader)相关的异常也很有帮助. 类加载器委托机制 Java类的装载是通过类加 ...

最新文章

  1. Swift 异常处理
  2. 在linux下创建自定义service服务
  3. mysql5.5.8编译安装_MySQL5.5.8源代码编译安装
  4. hibernate乐观锁_Hibernate Collection乐观锁定
  5. TDD开发模式实现代码功能逻辑(自己总结,持续更新)
  6. BugkuCTF-Reverse题easy-100(LCTF)
  7. Mybatis的基本步骤
  8. 将 React 作为 UI 运行时
  9. C#找出接口的所有实现类并遍历执行这些类的公共方法
  10. ubuntu之路——day9.2 Covariate shift问题和Batch Norm的解决方案
  11. 单目深度估计综述(updating...)
  12. Python爬虫项目分享一:《爬取周杰伦的歌曲清单》
  13. 大一计算机专业学生自我月小结,大学生学习过程月度总结
  14. Joint Extraction of Entities and Relations Based on a Novel Tagging Scheme阅读总结
  15. recap 360.html,Autodesk工程扫描数据软件ReCap 360 Pro 2018 R2 Win
  16. 关于“微信公众平台测试号管理接口配置信息配置失败”的问题解决办法
  17. 华为p40pro android11,华为P40Pro上手对比iPhone11Pro:差距拉开、黑马逆袭!
  18. 天蝎座2006年运程
  19. 用计算机写作文的好处,用计算机写作文.ppt
  20. 网上做什么挣钱?7个能赚钱的项目,个个都能发财!

热门文章

  1. 64位装32位oracle软件,在64位linux下安装32位oracle软件
  2. onlaunch 异步_微信小程序之onLaunch与onload异步问题
  3. JAVA获取安卓系统下usb_Android 获取 usb 权限的两种方法
  4. php mysql sqlite3_PHP连接SQLite数据库
  5. linux 扩lv文件系统,Linux LVM系列(五)lv xfs文件系统在线扩容
  6. 台州银行笔试考什么_台州银行笔试题型招聘考试真题考什么
  7. java 字符串池_什么是Java字符串池?
  8. bokeh python_Python Bokeh数据可视化教程
  9. JavaSpring框架有哪些优势?
  10. spring boot缓存excel临时文件后再操作