ClassLoader 深入解析(手工打造)

上传人:jin****ng 文档编号:200036181 上传时间:2023-04-13 格式:DOCX 页数:10 大小:89.33KB
返回 下载 相关 举报
ClassLoader 深入解析(手工打造)_第1页
第1页 / 共10页
ClassLoader 深入解析(手工打造)_第2页
第2页 / 共10页
ClassLoader 深入解析(手工打造)_第3页
第3页 / 共10页
点击查看更多>>
资源描述
本文是根据是在北京圣思园深入 JAVA 虚拟机系列视频的基础上自己整理而来,内 容范围没有超过其系列所述,在此给予说明。在进入ClassLoader的分析之前我们先看一个JAVA程序例子。class Singleton /* case 1 */privatestatic Singleton singleton = new Singleton。;publicstaticint counterl;publicstaticint counter2 = 0;/*case2*publicstaticintcounter1=0;*publicstaticintcounter2=0;*privatestaticSingletonsingleton=newSingleton();*/private Singleton() counterl +;counter2 +;publicstatic Singleton getInstance() returnsingleton ;publicclass MyTest publicstaticvoid main(String args) Singleton singleton = Singleton. getInstance ();System.out.println(counterl = + singleton. counterl);System.out.println(counter2 = + singleton. counter?,);/* result in case 1:* counterl = 1* counter2 = 0* result in case 2:* counter = 1* counter2 = 1*/上面的代码在easel与case2条件下运行结果却不一样,仅仅由于private static Singleton singleton = new Singleton();位置不同,要想了解其中的原因需要从类的使用时JVM完成的动作 说起,在一个类被JVM使用时大致经历如下三步(加载一链接一初始化)那么一个类被JVM使用时,必须预先经历如上图所述的加载过程。那么什么样的条件才会触 发上述过程的执行呢? JAVA程序使用类分为主动使用和被动使用,在JVM的实现规范中要 求,所有类的“主动使用“虚拟机才执行上述过程初始化相应的类,那么问题就归结为“主 动使用”的意义。1. 创建类的实例。 Object A = new ClassA();2. 访问某个类或接口的静态变量或对静态变量赋值。如Class Astatic a访问A.a时。需要指出的是访问类的static final int x = 0 (编译时常量)并不被认为是类的主动使 用,同样的假如有条件Class A extends B;Bstatic a如果使用A.a时只会初始化类B, 这种情况被认为是对父类的主动使用。3. 调用类的静态方法4. 使用反射机制(Class.ForName(xxx),而ClassLoader.load(并不会初始化类)5. 初始化一个类的子类时,父类也被主动使用6. 启动类( java TestMain)下面文章将针对上述过程给出比较详细的说明。加载过程总的来说类的加载是 JVM 使用类加载器(如系统类加载器、扩展加载器、根加载器) 在特定的加载路径里寻找class文件,并将class文件中的二进制数据读入到内存中,其中class 的数据结构被放置在运行时数据区的方法区类,并且在堆区里创建该类的Class对象,用来 封装类的数据结构信息。其中类加载类的方式有:文件系统加载、网络加载、zip jar归档文 件加载、数据库中提取、动态编译的源文件加载,下图显示的是类主动化使用的内存结构。Heap方法区主动使用片旧程序Clmss 对象从该示意图可以看出,类加载的最终产品是位于堆区中的 Class 对象,其封装了类在方 法区内数据结构,并且向 Java 程序员提供了访问方法区内数据结构的接口,需要指出的是, 类的加载并不都是主动使用时才加载,加载器可以实现为有预加载功能,如使用一定的算法 预测类的使用。在上面的叙述中我们提到过JVM使用类加载器对class文件进行加载(本文 后面部分将着重描述类的加载机制)。连接过程类加载后,就是连接阶段了,连接就是将已经读入到内存的类的二进制数据合并到虚拟 机的运行环境中去。连接的第一个阶段是类的验证,验证的内容如下:1. 类文件结构的检查,确保类的文件遵循java类文件的固定格式。2. 语义检查:确保类本身符合 java 语言的语法规定,比如验证 final 类型没有子类, final 方法没有被从写, private 没有被重写。3. 字节码验证:确定字节码流可以被 java 虚拟机安全的执行。4. 二进制兼容验证:确保相互应用的类之间协调一致。做完验证之后就是类的准备阶段,完成的工作为类的静态变量分配内存并设置为初始值 如有类 SampleStatic int a = 1;Static long b;系统会为a分配4个字节,并设置初始值为0,为b分配8个字节并设置初始值为0.做完类的准备工作之后就是类的解析,主要工作就是把类中的二进制中的符号引用替换 为直接引用。我们举个例子 void show objectA.print();objectA.print() 就是对 ClassA 的一个 符号引用,经过解析之后该处的代码会被一个指向ClassA中方法区print方法的指针。初始化过程下面是类的初始化过程,初始化的主要步骤为:检查该类是否已经被加载和连接;如果 该类有父类,且没有初始化,对父类进行加载连接初始化;假如类中存在初始化语句,依次 执行初始化语句。而静态变量的声明以及静态代码块都被看作是类的初始化语句, java 虚拟 机会按照初始化语句在类文件中的顺序依次来执行他们。如static int a =1,与static a = 3这 样的语句都会被 JVM 顺序执行。前面提到,当 JVM 初始化一个类时要求他的父类已经被初始化,这样的机制并不适用 于接口,初始化一个类时,它实现的接口并不需要被初始化,初始化一个接口时,其父接口 也不需要被初始化,只有当程序首次使用接口中的静态变量时,才会导致接口的初始化。通过上面的论述,我们大致对类的使用有了一个初步的了解,接下来我们将分析本文开 始时提出的那个程序的运行结果在 case 1 中,在 M yTest那种使用 Singleton singleton = Singleton.get/nstance();这样的语句 为类的主动使用这会触发Singleton类的加载连接初始化。privatestatic Singleton singleton = new Singleton(); (A)publicstaticintcounterl; ( B)publicstaticintcounter2 = 0; ( C)在加载完后的连接阶段的准备期,会为singleton分配内存,设定默认值为null, counterl默认 值为0,counter2默认值为0.进入初始化阶段,第一步为singleton赋值会调用Singleton的 构造方法,此时执行counter1+,counter2+ counter1 = 1,counter2 = 1 ;第二步为counter1 赋 值,由于没有赋值语句counter1仍为1;第三步为counter2赋值,counter2被赋值为0,所 以结果是counter1 =1Counter2 =0在case2中使用如下语句publicstaticintcounterl; ( A)publicstaticintcounter2 = 0; (B)privatestatic Singleton singleton = new Singleton(); (C) 连接准备阶段结束后counter1 = 0,counter2 = 0,singleton = null;初始化时第一步A语句不用初 始化counter1 =0;第二部B语句初始化为0, counter2 =0 ;第三步调用构造函数counter1+, counter2+counter1 = 1,counter2 = 1 ; 所以case2的结果为 counter1 =1 counter2 = 1在下面的部分,我将给大家深入的介绍一下JVM的类加载器。类加载器分析在上面的例子中我们看到一个类的主动使用会经历加载、链接、初始化的过程,类的加载需要使用JVM的类加载器,JVM的类加载器使用父亲委托机制。我们首先看看JVM的类加载器树形图:用户自 走乂_艾口瑟器从上图可以看出, JVM 的类加载器一共有四大类,其中根类加载器、扩展类加载器、系统类 加载器(应用类加载器)为JVM自带的类加载器。1. 根类加载器。负责加载虚拟机的核心类库,如java.lang.*等。根类加载器从系统属性 sun.boot.class.path 所指定的目录中加载类库。根类加载器的实现依赖于底层操作系统, 属于jvm实现的一部分,使用C+语言编写。它并没有继承ClassLoader类,也没有父加 载器。使用如 stringObject.getClass().getClassLoader()将返回 null。2. 扩展类加载器(Ext)。从图中可以看出它的父加载器是根加载器。它从java.ext.dirs系统 属性所指定的目录中加载类库,或者从jdk的安装目录的jrelibext子目录下加载类库, 如果你把用户创建的 jar 放在这个目录下会被扩展类加载器加载。扩展类加载器使用纯 java 实现,继承了 ClassLoader。3. 系统类加载器,也叫应用类加载器(APP),它的父加载器是Ext加载器。它从环境变量 里(安装JDK时设立的classpath)或从系统属性java.class.path加载类,它是用户自定 义的类加载器的默认父加载器,采用纯java实现,继承自ClassLoader。4. 用户自定义加载器。系统类加载器的子类,必须要继承自ClassLoader之类,并且重写 findClass 方法。假设我们自定义ClassLoader为loadA,并且使用loadA.load(Class),所谓的父亲委托机制 就是loadA委托父加载器(App)加载Class,App委托Ext,Ext委托BootStrap,如果BootStrap 不能加载,则让Ext加载,逐级下发,如果直到loadA还不能加载Class这抛出ClassNotFindException。委托机制是SUN公司基于安全性考虑的,这样可以保证Object这样 的重要类只能有JVM加载。我们定义若一个类加载器能够成功加载类Class,我们则称这个 加载器为该类的定义加载器,其下的子加载器为初始化加载器。如在上述的类之中,假设 App类加载器加载了类Class这App为定义加载器,APP与LoadA为初始化加载器。需要指出的是加载器之间的父子关系并不是指类之间的继承关系,而是指加载器之间的 包装关系。一对父子可能是同一个类加载器的实例,也可能不是。例如我们自定义类加载器 MyClassLoader。 LoadA = new MyClassLoader(); LoadB = new MyClassLoader(loadA), 我们称 loadB 包装了 loadA, LoadA 是 loadB 的父加载器。运行时包决定了 protecetd类和protected成员是否能够访问。我们知道所有的protected 的成员需要同一个包下的类才能访问。如果我们定义java.lang.Spy类,我们是否就能访问 java.lang.*下的核心protected资源呢?运行时包包括,包名相同,类加载器相同,所有 java.lang.Spy 与 java.lang.* 不在相同的运行时包,答案是否定的。下面我们以视频中一个详细的例子来讲述用户自定义加载器的实现。首先给出例子中类 加载器的树形关系图:在例子中我们定义三个类加载器,在重写的findClass方法中设定好加载路径。JDK API给出一个自定义ClassLoad的方法:classMyClassLoader extends ClassLoader public Class findClass(String name) byte b = loadClassData(name);returndefineClass(name, b, 0, b.length);private byte loadClassData(String name) / load the class data from the connection从上面的代码可以看出通过重写findClass方法并在loadClassData中设定好加载.class文 件的路径可以实现自定义的加载机制。假设我们已经定义好自己的加载器MyClassLoader,我们使用如下的代码便能够构造出 例子中的类加载器树形结构。MyClassLoaderloadB = new MyClassLoader(“loadB);loadB.setPath(“D:/app/serverlib);MyClassLoaderloadA = new MyClassLoader(loadB,“loadA);loadB.setPath(“D:/app/cientlib);MyClassLoaderloadC = new MyClassLoader(null,loadC)/null 代表父加载器为 BootstraploadC.setPath(D:/app/otherlib”);我们一个测试类Sample和一个测试类Dog进行加载测试。Class SampleStaticNew Dog();Class文件的存放路径如下图:TestCasel :Class clazz = loadA .Io adClass(“ Sample”);Clazz.newlnstance();TestCase2 :Class clazz = loadB .lo adClass(Sample”);Clazz.newInstance();TestCase3 :Class clazz = loadC .lo adClass(Sample”);Clazz.newInstance();在easel中由加载器的树形结构可以看出:loadA加载Sample时委托父亲LoadB加载 Sample,由于再向上委托并不能加载Sample,所以Sample由LoadB在app/serverlib下加载,对于Dog类,LoadA的所有父加载器都不能加载,所以有loadA在app/clientlib下加载在ease2中有loadB在app/serverlib中加载Sample,由于loadB与其父加载器都不能 加载Dog,所以会抛出ClassNotfoundException。此时如果把Dog拷贝到syslib下,Dog类就 会被appCloader加载,而不会出现ClassNotFound错误。在Case3中LoadC直接委托Bootstrap加载Sample,由于无法加载只能有自己加载,所 以 Sample 与 Dog 都会从 app/otherlib/下加载.假设我们在 MyClassLoader 中写意给 main 方法测试TestCase4 :Class clazz = loadA .Io adClass(“ Sample”);Sample sample =Clazz.newlnstance();此时会导致一个NoClassDefError,这主要是JVM的类加载器命名空间规则导致的,在jvm 中子加载器的命令空间包含了父加载器加载的所有类,反过来则不成立,因为MyClassLoader类是有appLoader加载的,所以其看不见有LoadB与loadA加载的类。在这里顺便提一下,在一个类主动使用时,该类就开始起生命周期加载,链接,初始化, 使用,卸载。Jvm自带的加载器加载的Class是不能够被卸载的,只有用户自定义加载器加 载的类才能被卸载,卸载机制是根据对类的应用情况而定,这与GC根据引用情况回收垃圾 差不多。Appendix: 视频下载地址ed2k:/|file|%E5%8C%97%E4%BA%AC%E5%9C%A3%E6%80%9D%E5%9B%AD%E6%B7%B1%E5% 85%A5Java%E8%99%9A%E6%8B%9F%E6%9C%BA_001_%E6%B7%B1%E5%85%A5%E8%AF%A6 %E8%A7%A3JVM%E4%B9%8B%E7%B1%BB%E5%8A%A0%E8%BD%BD%E5%99%A8%E6%B7%B 1%E5%BA%A6%E5%89%96%E6%9E%90%E3%80%81%E7%B1%BB%E7%9A%84%E4%B8%BB%E 5%8A%A8%E4%BD%BF%E7%94%A8%E3%80%81%E8%A2%AB%E5%8A%A8%E4%BD%BF%E7% 94%A8.rar|63026902|a8250012f7cd4b2ae287298280ab43fc|h=vlp6ulyhl24uetl542mv5n2r4rk sp3lc|/ed2k:/|file|%E5%8C%97%E4%BA%AC%E5%9C%A3%E6%80%9D%E5%9B%AD%E6%B7%B1%E5% 85%A5Java%E8%99%9A%E6%8B%9F%E6%9C%BA_002_%E6%B7%B1%E5%85%A5%E8%AF%A6 %E8%A7%A3JVM%E4%B9%8B%E7%B1%BB%E5%8A%A0%E8%BD%BD%E5%99%A8%E6%B7%B 1%E5%BA%A6%E5%89%96%E6%9E%90%E3%80%81%E6%A0%B9%E3%80%81%E6%89%A9%E 5%B1%95%E5%8F%8A%E7%B3%BB%E7%BB%9F%E7%B1%BB%E5%8A%A0%E8%BD%BD%E5% 99%A8.rar|68752084|536906e0af34020ad0e8d7cc5c27a885|h=mhx3suno2g7lfdxuv7ulkfoy7f uffi6h|/ed2k:/|file|%E5%8C%97%E4%BA%AC%E5%9C%A3%E6%80%9D%E5%9B%AD%E6%B7%B1%E5% 85%A5Java%E8%99%9A%E6%8B%9F%E6%9C%BA_003_%E7%BC%96%E8%AF%91%E5%B8%B8 %E9%87%8F%E3%80%81ClassLoader%E7%B1%BB%E3%80%81%E7%B3%BB%E7%BB%9F%E7% B1%BB%E5%8A%A0%E8%BD%BD%E5%99%A8%E6%B7%B1%E5%BA%A6%E6%8E%A2%E6%9E %90.rar|59768034|5e146db0c935a27ed92ee5c0e39ff697|h=ozku7ev5eujkdquog5nhobjnuy7iri6r|/ed2k:/|file|%E5%8C%97%E4%BA%AC%E5%9C%A3%E6%80%9D%E5%9B%AD%E6%B7%B1%E5% 85%A5Java%E8%99%9A%E6%8B%9F%E6%9C%BA_004_%E7%B1%BB%E5%8A%A0%E8%BD%B D%E5%99%A8%E7%9A%84%E7%88%B6%E4%BA%B2%E5%A7%94%E6%89%98%E6%9C%BA%E 5%88%B6%E6%B7%B1%E5%BA%A6%E8%AF%A6%E8%A7%A3.rar|62052459|967e9b5401f440 605e692eab997c3dd2|h=ywaj6rtsncd6xn2ywdvmzmjwxn66mkry|/ed2k:/|file|%E5%8C%97%E4%BA%AC%E5%9C%A3%E6%80%9D%E5%9B%AD%E6%B7%B1%E5% 85%A5Java%E8%99%9A%E6%8B%9F%E6%9C%BA_005_%E7%94%A8%E6%88%B7%E8%87%AA %E5%AE%9A%E4%B9%89%E7%B1%BB%E5%8A%A0%E8%BD%BD%E5%99%A8%E6%8E%A2%E 7%A7%98.rar|49971750|94802abd94af4b3300020f8403b4aea5|h=6zwpp2oyjav73ov3oqdhpa sxfh6hg4ou|/ed2k:/|file|%E5%8C%97%E4%BA%AC%E5%9C%A3%E6%80%9D%E5%9B%AD%E6%B7%B1%E5%85%A5Java%E8%99%9A%E6%8B%9F%E6%9C%BA_006_%E8%87%AA%E5%AE%9A%E4%B9%89 %E7%B1%BB%E5%8A%A0%E8%BD%BD%E5%99%A8%E4%B8%8EJVM%E5%86%85%E7%BD%AE%E5%8A%A0%E8%BD%BD%E5%99%A8%E4%BA%A4%E4%BA%92%E8%AF%A6%E6%9E%90.ra r|54167320|67e322dc10a21031ea5997300d9795f5|h=jkfvr7cszoqy6xtbeouonfk74vpfbzx2|/ed2k:/|file|%E5%8C%97%E4%BA%AC%E5%9C%A3%E6%80%9D%E5%9B%AD%E6%B7%B1%E5% 85%A5Java%E8%99%9A%E6%8B%9F%E6%9C%BA_007_ClassLoader%E7%B1%BB%E6%BA%90 %E4%BB%A3%E7%A0%81%E6%B7%B1%E5%BA%A6%E5%89%96%E6%9E%90%E5%8F%8A%E7 %B1%BB%E7%9A%84%E5%8D%B8%E8%BD%BD%E6%9C%BA%E5%88%B6%E8%AF%A6%E8%A 7%A3.rar|63518521|39bb0ff86b366ed5a4abb7d9e1bf686a|h=wmtllq7wxcpdqv6p6hnbx74eb m7j7elk|/ed2k:/|file|%E5%8C%97%E4%BA%AC%E5%9C%A3%E6%80%9D%E5%9B%AD%E6%B7%B1%E5%85%A5Java%E8%99%9A%E6%8B%9F%E6%9C%BA_ClassLoader.pdf|3308957|8ce34dbb769d488d220e03f161cd2660|h=zoexx43fhwe3nozs7lg6vaj5kigtu3r4|/
展开阅读全文
相关资源
相关搜索

最新文档


当前位置:首页 > 图纸设计 > 毕设全套


copyright@ 2023-2025  zhuangpeitu.com 装配图网版权所有   联系电话:18123376007

备案号:ICP2024067431-1 川公网安备51140202000466号


本站为文档C2C交易模式,即用户上传的文档直接被用户下载,本站只是中间服务平台,本站所有文档下载所得的收益归上传人(含作者)所有。装配图网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对上载内容本身不做任何修改或编辑。若文档所含内容侵犯了您的版权或隐私,请立即通知装配图网,我们立即给予删除!