ImageVerifierCode 换一换
格式:DOCX , 页数:30 ,大小:183.18KB ,
资源ID:12851515      下载积分:3 金币
快捷下载
登录下载
邮箱/手机:
温馨提示:
快捷下载时,用户名和密码都是您填写的邮箱或者手机号,方便查询和重复下载(系统自动生成)。 如填写123,账号就是123,密码也是123。
特别说明:
请自助下载,系统不会自动发送文件的哦; 如果您已付费,想二次下载,请登录后访问:我的下载记录
支付方式: 支付宝    微信支付   
验证码:   换一换

加入VIP,免费下载
 

温馨提示:由于个人手机设置不同,如果发现不能下载,请复制以下地址【https://www.bdocx.com/down/12851515.html】到电脑端继续下载(重复下载不扣费)。

已注册用户请登录:
账号:
密码:
验证码:   换一换
  忘记密码?
三方登录: 微信登录   QQ登录  

下载须知

1: 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。
2: 试题试卷类文档,如果标题没有明确说明有答案则都视为没有答案,请知晓。
3: 文件的所有权益归上传用户所有。
4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
5. 本站仅提供交流平台,并不能对任何下载内容负责。
6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

版权提示 | 免责声明

本文(jvm ClassLoader详解.docx)为本站会员(b****0)主动上传,冰豆网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对上载内容本身不做任何修改或编辑。 若此文所含内容侵犯了您的版权或隐私,请立即通知冰豆网(发送邮件至service@bdocx.com或直接QQ联系客服),我们立即给予删除!

jvm ClassLoader详解.docx

1、jvm ClassLoader详解JVM类加载 本文是上篇JVM详解的后续.讲解JVM的ClassLoader子系统原理. 若有疑问目录1.Java虚拟机类加载器结构简述 21.1JVM三种预定义类型类加载器 21.2类加载双亲委派机制介绍和分析 21.3类加载双亲委派示例 31.4java程序动态扩展方式 31.5常见问题分析 32.再分析类加载 3 1.Java虚拟机类加载器结构简述1.1JVM三种预定义类型类加载器我们首先看一下JVM预定义的三种类型类加载器,当一个 JVM 启动的时候,Java 缺省开始使用如下三种类型类装入器:启动(Bootstrap)类加载器:引导类装入器是用本地代

2、码实现的类装入器,它负责将 /lib 下面的类库加载到内存中。由于引导类加载器涉及到虚拟机本地实现细节,开发者无法直接获取到启动类加载器的引用,所以不允许直接通过引用进行操作。标准扩展(Extension)类加载器:扩展类加载器是由 Sun 的 ExtClassLoader(sun.misc.Launcher$ExtClassLoader) 实现的。它负责将 /lib/ext 或者由系统变量 java.ext.dir 指定位置中的类库加载到内存中。开发者可以直接使用标准扩展类加载器。系统(System)类加载器:系统类加载器是由 Sun 的 AppClassLoader(sun.misc.La

3、uncher$AppClassLoader)实现的。它负责将系统类路径(CLASSPATH)中指定的类库加载到内存中。开发者可以直接使用系统类加载器。除了以上列举的三种类加载器,还有一种比较特殊的类型就是线程上下文类加载器,这个将在后面单独介绍。a. Bootstrap ClassLoader/启动类加载器主要负责jdk_home/lib目录下的核心 api 或 -Xbootclasspath 选项指定的jar包装入工作.b. Extension ClassLoader/扩展类加载器主要负责jdk_home/lib/ext目录下的jar包或 -Djava.ext.dirs 指定目录下的jar包

4、装入工作c. System ClassLoader/系统类加载器主要负责java -classpath/-Djava.class.path所指的目录下的类与jar包装入工作.d. User Custom ClassLoader/用户自定义类加载器(java.lang.ClassLoader的子类)在程序运行期间, 通过java.lang.ClassLoader的子类动态加载class文件, 体现java动态实时类装入特性.1.2类加载双亲委派机制介绍和分析 在这里,需要着重说明的是,JVM在加载类时默认采用的是双亲委派机制。通俗的讲,就是某个特定的类加载器在接到加载类的请求时,首先将加载任务委

5、托给父类加载器,依次递归,如果父类加载器可以完成类加载任务,就成功返回;只有父类加载器无法完成此加载任务时,才自己去加载。关于虚拟机默认的双亲委派机制,我们可以从系统类加载器和标准扩展类加载器为例作简单分析。 图一 标准扩展类加载器继承层次图 图二 系统类加载器继承层次图 通过图一和图二我们可以看出,类加载器均是继承自java.lang.ClassLoader抽象类。我们下面我们就看简要介绍一下java.lang.ClassLoader中几个最重要的方法:/加载指定名称(包括包名)的二进制类型,供用户调用的接口public Class loadClass(String name) throws

6、 ClassNotFoundException/加载指定名称(包括包名)的二进制类型,同时指定是否解析(但是,这里的resolve参数不一定真正能达到解析的效果_),供继承用protectedsynchronized Class loadClass(String name, boolean resolve) throws ClassNotFoundException/findClass方法一般被loadClass方法调用去加载指定名称类,供继承用protected Class findClass(String name) throws ClassNotFoundException /定义类型,

7、一般在findClass方法中读取到对应字节码后调用,可以看出不可继承(说明:JVM已经实现了对应的具体功能,解析对应的字节码,产生对应的内部数据结构放置到方法区,所以无需覆写,直接调用就可以了)protected final Class defineClass(String name, byte b, int off, int len)throws ClassFormatError/ 通过进一步分析标准扩展类加载器(sun.misc.Launcher$ExtClassLoader)和系统类加载器(sun.misc.Launcher$AppClassLoader)的代码以及其公共父类(.URL

8、ClassLoader和java.security.SecureClassLoader)的代码可以看出,都没有覆写java.lang.ClassLoader中默认的加载委派规则-loadClass()方法。既然这样,我们就可以通过分析java.lang.ClassLoader中的loadClass(String name)方法的代码就可以分析出虚拟机默认采用的双亲委派机制到底是什么模样:public Class loadClass(String name)throws ClassNotFoundException return loadClass(name,false);protectedsy

9、nchronized Class loadClass(String name,boolean resolve) throws ClassNotFoundException /首先判断该类型是否已经被加载 Class c = findLoadedClass(name); if (c =null) /如果没有被加载,就委托给父类加载或者委派给启动类加载器加载 try if (parent !=null) /如果存在父类加载器,就委派给父类加载器加载 c = parent.loadClass(name,false); else /如果不存在父类加载器,就检查是否是由启动类加载器加载的类,通过调用本地

10、方法native Class findBootstrapClass(String name) c = findBootstrapClass0(name); catch (ClassNotFoundException e) /如果父类加载器和启动类加载器都不能完成加载任务,才调用自身的加载功能 c = findClass(name); if (resolve) resolveClass(c); return c; 通过上面的代码分析,我们可以对JVM采用的双亲委派类加载机制有了更感性的认识,下面我们就接着分析一下启动类加载器、标准扩展类加载器和系统类加载器三者之间的关系。可能大家已经从各种资料上

11、面看到了如下类似的一幅图片: 图三 类加载器默认委派关系图上面图片给人的直观印象是系统类加载器的父类加载器是标准扩展类加载器,标准扩展类加载器的父类加载器是启动类加载器,下面我们就用代码具体测试一下:示例代码:public static void main(String args) try System.out.println(ClassLoader.getSystemClassLoader(); System.out.println(ClassLoader.getSystemClassLoader().getParent(); System.out.println(ClassLoader.g

12、etSystemClassLoader().getParent().getParent(); catch (Exception e) e.printStackTrace(); 说明:通过java.lang.ClassLoader.getSystemClassLoader()可以直接获取到系统类加载器。代码输出如下:sun.misc.Launcher$AppClassLoader197d257sun.misc.Launcher$ExtClassLoader7259danull 通过以上的代码输出,我们可以判定系统类加载器的父加载器是标准扩展类加载器,但是我们试图获取标准扩展类加载器的父类加载器时

13、确得到了null,就是说标准扩展类加载器本身强制设定父类加载器为null。我们还是借助于代码分析一下: 我们首先看一下java.lang.ClassLoader抽象类中默认实现的两个构造函数: protected ClassLoader() SecurityManager security = System.getSecurityManager(); if (security !=null) security.checkCreateClassLoader(); /默认将父类加载器设置为系统类加载器,getSystemClassLoader()获取系统类加载器 this.parent = get

14、SystemClassLoader(); initialized =true; protected ClassLoader(ClassLoader parent) SecurityManager security = System.getSecurityManager(); if (security !=null) security.checkCreateClassLoader(); /强制设置父类加载器 this.parent = parent; initialized =true; 我们再看一下ClassLoader抽象类中parent成员的声明: / The parent class l

15、oader for delegationprivate ClassLoaderparent;声明为私有变量的同时并没有对外提供可供派生类访问的public或者protected设置器接口(对应的setter方法),结合前面的测试代码的输出,我们可以推断出:1 系统类加载器(AppClassLoader)调用ClassLoader(ClassLoader parent)构造函数将父类加载器设置为标准扩展类加载器(ExtClassLoader)。(因为如果不强制设置,默认会通过调用getSystemClassLoader()方法获取并设置成系统类加载器,这显然和测试输出结果不符。)2 扩展类加载器

16、(ExtClassLoader)调用ClassLoader(ClassLoader parent)构造函数将父类加载器设置为null。(因为如果不强制设置,默认会通过调用getSystemClassLoader()方法获取并设置成系统类加载器,这显然和测试输出结果不符。) 现在我们可能会有这样的疑问:扩展类加载器(ExtClassLoader)的父类加载器被强制设置为null了,那么扩展类加载器为什么还能将加载任务委派给启动类加载器呢? 图四 标准扩展类加载器和系统类加载器成员大纲视图 图五扩展类加载器和系统类加载器公共父类成员大纲视图 通过图四和图五可以看出,标准扩展类加载器和系统类加载器及

17、其父类(.URLClassLoader和java.security.SecureClassLoader)都没有覆写java.lang.ClassLoader中默认的加载委派规则-loadClass()方法。有关java.lang.ClassLoader中默认的加载委派规则前面已经分析过,如果父加载器为null,则会调用本地方法进行启动类加载尝试。所以,图三中,启动类加载器、标准扩展类加载器和系统类加载器之间的委派关系事实上是仍就成立的。(在后面的用户自定义类加载器部分,还会做更深入的分析)。1.3类加载双亲委派示例以上已经简要介绍了虚拟机默认使用的启动类加载器、标准扩展类加载器和系统类加载器,

18、并以三者为例结合JDK代码对JVM默认使用的双亲委派类加载机制做了分析。下面我们就来看一个综合的例子。首先在eclipse中建立一个简单的java应用工程,然后写一个简单的JavaBean如下:package classloader.test.bean; publicclass TestBean public TestBean() 在现有当前工程中另外建立一测试类(ClassLoaderTest.java)内容如下:测试一:publicclass ClassLoaderTest publicstaticvoid main(String args) try /查看当前系统类路径中包含的路径条目

19、System.out.println(System.getProperty(java.class.path);/调用加载当前类的类加载器(这里即为系统类加载器)加载TestBeanClass typeLoaded = Class.forName(classloader.test.bean.TestBean);/查看被加载的TestBean类型是被那个类加载器加载的 System.out.println(typeLoaded.getClassLoader(); catch (Exception e) e.printStackTrace(); 对应的输出如下:D:DEMOdevStudyClass

20、LoaderTestbinsun.misc.Launcher$AppClassLoader197d257(说明:当前类路径默认的含有的一个条目就是工程的输出目录)测试二:将当前工程输出目录下的/classloader/test/bean/TestBean.class打包进test.jar剪贴到/lib/ext目录下(现在工程输出目录下和JRE扩展目录下都有待加载类型的class文件)。再运行测试一测试代码,结果如下:D:DEMOdevStudyClassLoaderTestbinsun.misc.Launcher$ExtClassLoader7259da对比测试一和测试二,我们明显可以验证前面

21、说的双亲委派机制,系统类加载器在接到加载classloader.test.bean.TestBean类型的请求时,首先将请求委派给父类加载器(标准扩展类加载器),标准扩展类加载器抢先完成了加载请求。 测试三:将test.jar拷贝一份到/lib下,运行测试代码,输出如下:D:DEMOdevStudyClassLoaderTestbinsun.misc.Launcher$ExtClassLoader7259da 测试三和测试二输出结果一致。那就是说,放置到/lib目录下的TestBean对应的class字节码并没有被加载,这其实和前面讲的双亲委派机制并不矛盾。虚拟机出于安全等因素考虑,不会加载/

22、lib存在的陌生类,开发者通过将要加载的非JDK自身的类放置到此目录下期待启动类加载器加载是不可能的。做个进一步验证,删除/lib/ext目录下和工程输出目录下的TestBean对应的class文件,然后再运行测试代码,则将会有ClassNotFoundException异常抛出。有关这个问题,大家可以在java.lang.ClassLoader中的loadClass(String name, boolean resolve)方法中设置相应断点运行测试三进行调试,会发现findBootstrapClass0()会抛出异常,然后在下面的findClass方法中被加载,当前运行的类加载器正是扩展类

23、加载器(sun.misc.Launcher$ExtClassLoader),这一点可以通过JDT中变量视图查看验证。1.4java程序动态扩展方式Java的连接模型允许用户运行时扩展引用程序,既可以通过当前虚拟机中预定义的加载器加载编译时已知的类或者接口,又允许用户自行定义类装载器,在运行时动态扩展用户的程序。通过用户自定义的类装载器,你的程序可以装载在编译时并不知道或者尚未存在的类或者接口,并动态连接它们并进行有选择的解析。 运行时动态扩展java应用程序有如下两个途径:1.4.1 调用java.lang.Class.forName()这个方法其实在前面已经讨论过,在后面的问题2解答中说明了

24、该方法调用会触发那个类加载器开始加载任务。这里需要说明的是多参数版本的forName()方法:public static Class forName(String name, boolean initialize, ClassLoader loader) throws ClassNotFoundException这里的initialize参数是很重要的,可以觉得被加载同时是否完成初始化的工作(说明: 单参数版本的forName方法默认是不完成初始化的).有些场景下,需要将initialize设置为true来强制加载同时完成初始化,例如典型的就是利用DriverManager进行JDBC驱动程序

25、类注册的问题,因为每一个JDBC驱动程序类的静态初始化方法都用DriverManager注册驱动程序,这样才能被应用程序使用,这就要求驱动程序类必须被初始化,而不单单被加载.1.4.2 用户自定义类加载器通过前面的分析,我们可以看出,除了和本地实现密切相关的启动类加载器之外,包括标准扩展类加载器和系统类加载器在内的所有其他类加载器我们都可以当做自定义类加载器来对待,唯一区别是是否被虚拟机默认使用。前面的内容中已经对java.lang.ClassLoader抽象类中的几个重要的方法做了介绍,这里就简要叙述一下一般用户自定义类加载器的工作流程吧(可以结合后面问题解答一起看):1、首先检查请求的类型

26、是否已经被这个类装载器装载到命名空间中了,如果已经装载,直接返回;否则转入步骤22、委派类加载请求给父类加载器(更准确的说应该是双亲类加载器,真个虚拟机中各种类加载器最终会呈现树状结构),如果父类加载器能够完成,则返回父类加载器加载的Class实例;否则转入步骤33、调用本类加载器的findClass()方法,试图获取对应的字节码,如果获取的到,则调用defineClass()导入类型到方法区;如果获取不到对应的字节码或者其他原因失败,返回异常给loadClass(), loadClass()转抛异常,终止加载过程(注意:这里的异常种类不止一种)。 (说明:这里说的自定义类加载器是指JDK 1

27、.2以后版本的写法,即不覆写改变java.lang.loadClass()已有委派逻辑情况下)1.5常见问题分析1.5.1 由不同的类加载器加载的指定类型还是相同的类型吗?在Java中,一个类用其完全匹配类名(fully qualified class name)作为标识,这里指的完全匹配类名包括包名和类名。但在JVM中一个类用其全名和一个加载类ClassLoader的实例作为唯一标识,不同类加载器加载的类将被置于不同的命名空间.我们可以用两个自定义类加载器去加载某自定义类型(注意,不要将自定义类型的字节码放置到系统路径或者扩展路径中,否则会被系统类加载器或扩展类加载器抢先加载),然后用获取到

28、的两个Class实例进行java.lang.Object.equals()判断,将会得到不相等的结果。这个大家可以写两个自定义的类加载器去加载相同的自定义类型,然后做个判断;同时,可以测试加载java.*类型,然后再对比测试一下测试结果。1.5.2 在代码中直接调用Class.forName(String name)方法,到底会触发那个类加载器进行类加载行为?Class.forName(String name)默认会使用调用类的类加载器来进行类加载。我们直接来分析一下对应的jdk的代码:/java.lang.Class.java publicstatic ClassforName(String

29、 className)throws ClassNotFoundException return forName0(className,true, ClassLoader.getCallerClassLoader();/java.lang.ClassLoader.java/ Returns the invokers class loader, or null if none.static ClassLoader getCallerClassLoader() / 获取调用类(caller)的类型 Class caller = Reflection.getCallerClass(3); / This

30、 can be null if the VM is requesting it if (caller =null) returnnull; /调用java.lang.Class中本地方法获取加载该调用类(caller)的ClassLoader return caller.getClassLoader0();/java.lang.Class.java/虚拟机本地实现,获取当前类的类加载器,前面介绍的Class的getClassLoader()也使用此方法native ClassLoader getClassLoader0();1.5.3 在编写自定义类加载器时,如果没有设定父加载器,那么父加载器是?前面讲过,在不指定父类加载器的情况下,默认采用系统类加载器。可能有人觉得不明白,现在我们来看一下JDK对应的代码实现。众所周知,我们编写自定义的类加载器直接或者间接继承自java.lang.ClassLoader抽象类,对应的无参默认构造函数实现如下:/摘自java.lang.ClassLoader.javaprotecte

copyright@ 2008-2022 冰豆网网站版权所有

经营许可证编号:鄂ICP备2022015515号-1