类加载器--4
应用程序默认类加载器
验证类加载器委托机制
----------- android培训、java培训、java学习型技术博客、期待与您交流! ------------
1. 应用程序的默认类加载器
前面的日志主要阐述了某一个类加载器如何进行对指定类的加载。现在就要讨论一下当JVM真正执行用户的应用程序代码的时候,到底从哪一个类加载器开始对指定的类进行加载的。
1). 线程上下文类加载器
(1). Thread类的成员变量 contextClassLoader
[1]. private ClassLoader contextClassLoader;
[2]. 表示当前线程执行到的代码中涉及到的类由哪一个类加载器来加载。
(2). 操作成员变量contextClassLoader
[1]. 获取当前线程的上下文类加载器
publicClassLoader getContextClassLoader();
[2]. 为当前线程设置上下文类加载器
public voidsetContextClassLoader(ClassLoader paramClassLoader);
【注意】Thread类是一种JavaBean类,所以对应的字段有对应的Setter和Getter
2). Launcher简述
(1). Launcher类的类加载器为Bootstrap类加载器
[1]. Launcher类位于sun.misc这个包下面
[2]. Launcher.class文件位于jre/lib下的rt.jar内部,所以Launcher.class由Bootstrap类加载器进行加载。
(2). Launcher的部分源码
(3). 从Launcher构造方法看默认类加载器和线程上下文类加载器的关系
[1]. Bootstrap对Launcher的初始化
当执行java命令的时候,JVM会先使用Bootstrap类加载器载入并初始化一个Launcher实例,这样就会调用Launcher的构造方法
[2]. 首先JVM获取了ExtClassLoader的实例
[3]. 然后JVM向getAppClassLoader()传入ExtClassLoader的实例并获取到AppClassLoader的实例。
【注意】以上就是把AppClassLoader的parent属性设置为ExtClassLoader实例,构建默认类加载器的组织关系
[4]. 最后JVM通过setContextClassLoader()将当前线程的类加载器设置为AppClassLoader
【结论】因此AppClassLoader就是应用程序默认的类加载器
【结论】AppClassLoader和ExtClassLoader是由Bootstrap类加载器通过调用Launcher的构造方法来进行加载的。
2. 验证类加载器委托机制
(1). Class类的getClassLoader() 方法
[1]. 功能:获取加载这个类或者接口的类加载器
[2]. 方法原型:public ClassLoader getClassLoader();
[3]. 返回值:返回加载这个类或者接口的类加载器
【注意】如果这个类是由Bootstrap类加载器进行加载的,则getClassLoader()方法返回值就用null代表Bootstrap类加载器。
(2). 场景模拟
【目标】在AppClassLoader和ExtClassLoader加载管辖范围构建重名的字节码文件
[1]. 直接运行ClassLoaderTest.java的结果
sun.misc.Launcher$AppClassLoader@c4fe76
null
sun.misc.Launcher$AppClassLoader@c4fe76
【结果说明】
{1}. sun.misc.Launcher$AppClassLoader@c4fe76 说明加载ClassLoaderTest.class的类加载器是AppClassLoader类加载器
{2}. null说明加载System.class的类加载器是Bootstrap类加载器
【用null表示Bootstrap类加载器】
{3}. sun.misc.Launcher$AppClassLoader@c4fe76说明当前线程的上下文类加载器是AppClassLoader类加载器
[2]. 将上面图中的ClassLoader工程打包成ClassLoader.jar文件并放置到jre/lib/ext子目录下【这个文件夹下的jar中的class文件是ExtClassLoader的管辖范围】。
这样在CLASSPATH指定的当前MyEclipse的ClassLoader工程中存在ClassLoaderTest.class字节码文件【AppCLassLoader管辖范围】
AppClassLoader和ExtClassLoader管辖的范围内出现了重名的ClassLoaderTest.class字节码文件。
[3]. 再次运行ClassLoaderTest.class结果
sun.misc.Launcher$ExtClassLoader@11e1e67
null
sun.misc.Launcher$AppClassLoader@c4fe76
【结果说明】
{1}. sun.misc.Launcher$ExtClassLoader@11e1e67 说明加载ClassLoaderTest.class的类加载器是ExtClassLoader类加载器
{2}. null说明加载System.class的类加载器仍然是Bootstrap类加载器
{3}. sun.misc.Launcher$AppClassLoader@c4fe76说明当前线程的上下文类加载器是AppClassLoader类加载器
(2). 结果分析
[1]. 当jre/lib/ext下面没有ClassLoaderTest.jar时候,打印结果是
sun.misc.Launcher$AppClassLoader@c4fe76
null
sun.misc.Launcher$AppClassLoader@c4fe76
{1}. 这个运行过程相当于:xxxx>java ClassLoaderTest
{2}. java命令后面字节码ClassLoaderTest是第一个要被加载到内存的类。
{3}. JVM启动时会指示Bootstrap类加载器调用Launcher的构造方法并设置当前线程的类加载器为AppClassLoader。
此时JVM就要求AppClassLoader类加载器去加载java命令后的ClassLoaderTest
{4}. 由于AppClassLoader采用双亲委托机制对类ClassLoaderTest进行加载。
{4}1. 因此AppClassLoader将ClassLaderTest交给ExtClassLoader这个类加载器去加载。ClassLoaderTest将这个类交给Bootstrap类加载器进行加载。但是BootStrap和ExtClassLoader这两个类加载器都在指定的范围内找不到ClassLoaderTest.class这个文件,所以最后由AppClassLoader类加载器到工程的bin下面去查找这个类,并加载成功。
{4}2. 这样JVM指示主线程去运行AppClassLoader找到的ClassLoaderTest这个类的主方法。
运行结果表明
{1}.加载ClassLoaderTest这个类的类加载器是AppClassLoader去做的,所以返回的是AppClassLoader这个类加载器
{2}.加载System这个类的类加载器是Bootstrap去做的
{3}.当前线程使用的默认类加载器就是AppClassLoader
[2]. 当jre/lib/ext下面有ClassLoaderTest.jar时候,打印结果是
sun.misc.Launcher$ExtClassLoader@11e1e67
null
sun.misc.Launcher$AppClassLoader@c4fe76
{1}. JVM启动时当前线程的类加载器AppClassLoader去加载main方法所在的类ClassLoaderTest。
{2}. AppClassLoader采用双亲委托机制将加载任务丢给ExtClassLoader。ExtClassLoader也采取同样的措施将这个任务交给Bootstrap类加载器。但是Bootstrap没找到这个类名。就将任务转回ExtClassLoader。但是ExtClassLoader却在自己的管辖范围找到了ClassLoaderTest字节码文件。这个时候对ClassLoaderTest类的加载就完成了。
{3}. 所以JVM运行main方法的是被正确加载并成功解析的jre/lib/ext中的ClassLoader.jar中的ClassLoaderTest类,而不是MyEclipse中的ClassLoader工程下的bin中的ClassLoaderTest这个类。
{4}. JVM执行的就是jre/lib/ext中的ClassLoader.jar的ClassLoaderTest类。
sun.misc.Launcher$ExtClassLoader@11e1e67
{4}1. System.out.println(ClassLoaderTest.class.getClassLoader());
由于ClassLoaderTest被ExtClassLoader类加载器进行加载,所以getClassLoader()就返回加载这个类的类加载器 -----ExtClassLoader
null
{4}2. System.out.println(System.class.getClassLoader());
由于System类被Bootstrap类加载器进行加载,所以getClassLoader()就返回加载这个类的类加载器 ----- Bootstrap
sun.misc.Launcher$AppClassLoader@c4fe76
{4}3. System.out.println(Thread.currentThread().getContextClassLoader())
JVM启动调用Bootstrap初始化其他的类加载器,这个Bootstrap实际上是在实例化Launcher类的过程中,在Launcher类的构造方法中当前线程的上下文类加载器设置为AppClassLoader。所以这个打印返回时AppClassLoader。
【现象解释】
修改工程ClassLoader下的ClassLoaderTest.java并在MyEclipse中点击“RUN”运行修改后的ClassLoaderTest.class文件,但是运行结果仍然是
sun.misc.Launcher$ExtClassLoader@11e1e67
null
sun.misc.Launcher$AppClassLoader@c4fe76
不变。
{1}. 点击MyEclipse中“RUN”之后,相当于【之前的日志对MyEclipse对应的CMD格式有讨论过】
xxxx\ClassLoader\bin > java ClassLoaderTest
这样:在当前线程的类加载器AppClassLoader对ClassLoaderTest进行查找并加载的过程,ExtClassLoader就会率先在自己的管辖范围(jre/lib/ext) 里面找到这个ClassLoaderTest类。
{2}. JVM就会执行第一个被加载并解析好的CLassLoaderTest类中的main方法。但是MyEclipse中ClassLoader工程下的ClassLoaderTest.class实际上并没有加载到,那这个类就不能被使用。那么这个类中的main方法就不会被执行
{3}. 由于修改的是没有被加载的源文件,所以一直不能被运行!!
【JVM加载的第一个类】
JVM要加载的第一个类不是main方法中遇到的第一个类,而是java命令后面的第一个类名。(也就是main方法所在的类是JVM第一个要加载的类 )
【问题】为什么JVM会第一个去加载java命令后面的类名而不是第一个去加载main中遇到的第一个类名?
{1}. 在类的生命周期中,JVM明确规定这个类必须先被加载并正确解析之后才能使用。
{2}. Java中一个main方法一定是属于某一个类。所以要运行这个main方法,就必须使得这个类可以被使用,那就要求这个类已经先被夹在并解析成功。
【JVM执行main方法】
{1}. JVM第一次找到的是哪一个类,哪一个类中的main方法就被执行
{2}. 第一个被JVM加载的类就是java命令后面的第一个类名 (等价于:要执行的main方法所在的类名)
【注意】想使用一个类中成员必须在这个类被加载并正确解析之后才能使用。调用某个类的main方法也不例外。
----------- android培训、java培训、java学习型技术博客、期待与您交流! ------------