虚拟机设计团队把类加载阶段中的“通过一个类的全限定名来获取描述此类的二进制字节流”这个动作放到Java虚拟机外部实现,以便让应用程序自己决定如何去获取所需要的类,实现这个动作的代码块被称为“类加载器”

Java中的类加载器主要有2类,一类是系统提供的,另一类是由Java应用开发人员编写的,系统提供的类加载器主要有下面3个:

1,启动类加载器(Bootstarp ClassLoader)

将存放在<JAVA_HOME>\lib(JDK1.6)目录中的,或者被-Xbootclasspath参数所指定的路径中的,并且是虚拟机识别(仅按照文件名识别,如:rt.jar,名字不符合的类库即使放在lib目录中也不会被加载)的类库加载到虚拟机内存中

启动类加载器无法被Java程序直接引用

2,扩展类加载器(Extension ClassLoader)

负责加载<JAVA_HOME>\lib\ext目录中的,或者被java.ext.dirs系统变量所指定的路径中的所有类库

由sun.misc.Launcher$ExtClassLoader实现

开发者可以直接使用扩展类加载器

3,应用程序类加载器(Application ClassLoader)

负责加载用户类路径(ClassPath)上锁指定的类库

由sun.misc.Launcher$AppClassLoader实现

public class Test {
public static void main(String[] args){
System.out.println(ClassLoader.getSystemClassLoader());
}
}

结果为:

sun.misc.Launcher$AppClassLoader@addbf1

开发者可以直接使用扩展类加载器

4,自定义类加载器

除了系统提供的类加载器之外,开发人员可以通过继承java.lang.ClassLoader类并重写该类的findClass方法的方式实现自己的类加载器

//-XX:+TraceClassLoading
public class MyClassLoader extends ClassLoader{
@Override
public Class<?> findClass(String name) throws ClassNotFoundException {
System.out.println("Use myclassloader findClass method.");
//name = com.test.Test
//fileName = Test.class
String fileName = name.substring(name.lastIndexOf(".")+1)+".class";
byte[] bytes = loadClassData("e:\\"+fileName);
return defineClass(name, bytes, 0, bytes.length);
} public byte[] loadClassData(String name) {
try {
FileInputStream fileInput = new FileInputStream(new File(name));
ByteArrayOutputStream bytesOutput = new ByteArrayOutputStream();
int b = 0;
while ((b = fileInput.read()) != -1) {
bytesOutput.write(b);
}
return bytesOutput.toByteArray();
} catch (Exception e) {
e.printStackTrace();
}
return null;
} public static void main(String[] args){
MyClassLoader myClassLoader = new MyClassLoader();
try {
Class<? extends Object> testClass = myClassLoader.loadClass("com.test.Test");
Object obj = testClass.newInstance();
System.out.println(obj.getClass().getName());
System.out.println(obj.hashCode());
} catch (Exception e) {
e.printStackTrace();
}
}
}

将Test类编译后生成的Test.class文件放到e盘下

public class Test {
public Test(){}
}

运行结果为:

......
[Loaded com.test.MyClassLoader from file:/E:/eclipseProject/jvm/bin/]
Use myclassloader findClass method.
[Loaded java.io.ByteArrayOutputStream from shared objects file]
[Loaded com.test.Test from __JVM_DefineClass__]
com.test.Test
827574
......

从输出结果可以看到com.test.Test是由MyClassLoader类加载的

绝大部分Java程序都是由这4种类加载器相互配合进行加载的,它们之间的关系如下:

类加载器之间的这种层次关系,被称为类加载器的双亲委派模型。双亲委派模型要求除了顶层的启动类加载器外,其余的类加载器都要有自己的父类加载器

public class Test {
public static void main(String[] args){
ClassLoader loader = Test.class.getClassLoader();
while(null != loader){
System.out.println(loader.toString());
loader = loader.getParent();
}
}
}
sun.misc.Launcher$AppClassLoader@82ba41
sun.misc.Launcher$ExtClassLoader@923e30

第一个输出的为Test类的类加载器:应用程序类加载器,是sun.misc.Launcher$AppClassLoader类的一个实例;第二个输出的为扩展类加载器,是sun.misc.Launcher$ExtClassLoader类的一个实例;这里没有输出启动类加载器,原因是如果父类加载器为启动类加载器,getParent()方法将返回null

加载器之间的父子关系一般不使用继承来维护,而是通过组合复用父类加载器的代码

如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器去完成,每一个层次的类加载器都是如此,所有加载器的加载请求最终都应该传送到顶层的启动类加载器中,只有当父加载器反馈自己无法完成这个加载请求(它的搜索范围中没有找到所需的类)时,子加载器才会尝试自己去加载

可以看一下ClassLoader类中的loadClassInternal方法,虚拟机调用该方法加载类:

// This method is invoked by the virtual machine to load a class.
private synchronized Class loadClassInternal(String name)
throws ClassNotFoundException{
return loadClass(name);
} //Invoking this method is equivalent to invoking loadClass(name,false).
public Class<?> loadClass(String name) throws ClassNotFoundException {
return loadClass(name, false);
} //Subclasses of ClassLoader are encouraged to override findClass(String),
//rather than this method.
protected synchronized Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException{
// First, check if the class has already been loaded
Class c = findLoadedClass(name);
if (c == null) {
try {
if (parent != null) {
c = parent.loadClass(name, false);
} else {
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
// ClassNotFoundException thrown if class not found
// from the non-null parent class loader
}
if (c == null) {
// If still not found, then invoke findClass in order
// to find the class.
c = findClass(name);
}
}
if (resolve) {
resolveClass(c); }
return c;
}

通过loadClass方法的源代码可以看出,类加载器会先检查类是否已经被加载过,如果没有加载过则调用父类加载器加载该类(如果父类加载器为空则默认使用启动类加载器作为父类加载器),如果父类加载器加载失败,调用自己的findClass方法进行加载

比起重写loadClass方法,JDK更推荐通过重写findClass方法实现自定义类加载器(详见备注1)

来看看JDK对findClass方法的描述:

/**
* Finds the class with the specified binary name.
* This method should be overridden by class loader implementations that
* follow the delegation model for loading classes, and will be invoked by
* the loadClass method after checking the parent class loader
* for the requested class. The default implementation
* throws a ClassNotFoundException.
*
* @param name
* The binary name of the class
*
* @return The resulting Class object
*
* @throws ClassNotFoundException
* If the class could not be found
*
* @since 1.2
*/
protected Class<?> findClass(String name) throws ClassNotFoundException {
throw new ClassNotFoundException(name);
}

对loadClass方法中的resolveClass方法也比较好奇,顺带查看了下这个方法的作用:

/**Links the specified class.  This (misleadingly named) method may be
* used by a class loader to link a class. If the class has already
* been linked, then this method simply returns. Otherwise, the class
* is linked as described in the "Execution" chapter of the Java Language
* Specification.
*/
protected final void resolveClass(Class<?> c) {
resolveClass0(c);
} private native void resolveClass0(Class c);

可以发现虚拟机将调用该方法完成类的连接过程,类的连接过程详见:http://blog.csdn.net/a19881029/article/details/17068191

查看ClassLoader类的源代码会发现很多方法是通过调用本地方法(native修饰符修饰的方法)的方式实现的

使用双亲委派模型组织类加载器之间的关系的好处是:Java类随着它的类加载器一起具备了一种带有优先级的层次关系。例如:java.lang.Object类,它存放在rt.jar中,无论哪一个类加载器要加载这个类,最终都会委派给启动类加载器进行加载,启动类加载器在其搜索范围内可以搜索到的只有rt.jar中的java.lang.Object类(详见备注2),这样可以保证Object类始终由启动类加载器从rt.jar中的java.lang.Object加载得到,确保了Object类的唯一性(详见备注3)

如果没有使用双亲委派模型,由各个类加载器自行加载的话,如果用户自己实现一个名为java.lang.Object类,并用自定义的类加载器进行加载,系统中将出现多个不同的Object类,Java类型体系中最基础的行为将无法保证,应用程序也将变得一片混乱

备注:

1,为什么JDK不推荐通过重写loadClass方法实现自定义类加载器?

通过重写findClass方法实现自定义类加载器:当调用loadClass方法加载类时,由于自定义类加载器没有重写loadClass方法,实际调用的是ClassLoader类的loadClass方法,该方法保证如果父类能够加载所需加载的类,则把加载动作委托给父类完成,当所有父类都无法完成加载动作时,才把加载动作交由自定义类加载器的findClass方法完成,完全符合Java类加载器双亲委派模型的设计思路

通过重写loadClass方法实现自定义类加载器:当调用loadClass方法加载类时,将直接调用自定义类加载器中重写的loadClass方法完成加载动作,如果重写的loadClass方法中没有实现首先尝试将加载动作委托给父类完成这一过程,将打破双亲委派模型的设计思路,设计是可以被打破的,但是需要更好的理由(JDBC,JNDI就打破了双亲委派模型)

当然,如果在重写的loadCLass方法中首先尝试让父类加载器完成加载过程,则本质上也是没有没有问题的,只是依然别扭罢了,首先就是为什么不使用现成的实现?其次如果父类加载器无法完成加载动作,还是要把加载过程委托给自定义类加载器的findClass方法,关键问题是,在ClassLoader类中,findClass是一个空方法,也就是说你还是得重写自己的findClass方法,绕了一大圈,又回来了,除非你能确定父类加载器能够完成加载动作,这时将不会调用自定义类加载器的findClass方法,不过这样一来,你为什么要实现自己的类加载器?综上,使用重写findClass方法实现自定义的类加载器就对了,不过下面依然尝试了一下通过重写loadClass方法实现自定义类加载器:

//-XX:+TraceClassLoading
public class MyClassLoader extends ClassLoader{
@Override
public Class<?> loadClass(String name) throws ClassNotFoundException {
return super.loadClass(name);
} @Override
public Class<?> findClass(String name) throws ClassNotFoundException {
System.out.println("Use myclassloader findClass method.");
//name = com.test.Test
//fileName = Test.class
String fileName = name.substring(name.lastIndexOf(".")+1)+".class";
byte[] bytes = loadClassData("e:\\"+fileName);
return defineClass(name, bytes, 0, bytes.length);
} public byte[] loadClassData(String name) {
try {
FileInputStream fileInput = new FileInputStream(new File(name));
ByteArrayOutputStream bytesOutput = new ByteArrayOutputStream();
int b = 0;
while ((b = fileInput.read()) != -1) {
bytesOutput.write(b);
}
return bytesOutput.toByteArray();
} catch (Exception e) {
e.printStackTrace();
}
return null;
} public static void main(String[] args){
MyClassLoader myClassLoader = new MyClassLoader();
try {
Class<? extends Object> testClass = myClassLoader.loadClass("com.test.Test");
Object obj = testClass.newInstance();
System.out.println(obj.getClass().getName());
System.out.println(obj.hashCode());
} catch (Exception e) {
e.printStackTrace();
}
}
}

还是将Test.class文件放置在e盘下:

......
Use myclassloader findClass method.
[Loaded java.io.ByteArrayOutputStream from shared objects file]
[Loaded com.test.Test from __JVM_DefineClass__]
com.test.Test
827574
......

当然如果Test.class文件与MyCLassLoader.class文件放置在同一个路径下,应用程序类加载器(也就是MyClassLoader类加载器的父类加载器)将完成Test类的加载动作,此时不会跳进MyClassLoader类的findClass方法,运行结果如下:

......
[Loaded com.test.Test from file:/E:/eclipseProject/jvm/bin/]
com.test.Test
21174459
......

2,在自定义类加载器中,使用defineClass方法加载一个我自己实现的java.lang.Object类

package java.lang;

public class Object {
public Object(){}
}

运行时抛出下面的异常(被禁止的包名称):

java.lang.SecurityException: Prohibited package name: java.lang

事实上,加载所有以"java."开头的类都会抛出这个异常,这应该是出于JDK对其自身实现的基础类的保护

......
if ((name != null) && name.startsWith("java.")) {
throw new SecurityException("Prohibited package name: " +
name.substring(0, name.lastIndexOf('.')));
}
......

3,比较2个类是否相等,只有在这两个类是由同一个类加载器加载的前提之下才有意义,否则,即使这2个类是源于同一个Class文件,只要加载它们的类加载器不同,这2个类必定不相等(个人认为这是很容易理解的,同一个Class文件被2个不同的Java进程加载所产生的2个类肯定是不同的。判断2个类相等,最终判断的还是其指向的已分配内存区是否为同一个,对于2个独立的Java进程,其使用的内存空间是没有交集的)

JVM笔记7:类加载器的更多相关文章

  1. Java虚拟机JVM学习05 类加载器的父委托机制

    Java虚拟机JVM学习05 类加载器的父委托机制 类加载器 类加载器用来把类加载到Java虚拟机中. 类加载器的类型 有两种类型的类加载器: 1.JVM自带的加载器: 根类加载器(Bootstrap ...

  2. JVM的艺术—类加载器篇(二)

    分享是价值的传递,喜欢就点个赞 引言 今天我们继续来深入的剖析类加载器的内容.上节课我们讲了类加载器的基本内容,没看过的小伙伴请加关注.今天我们继续. 什么是定义类加载器和初始化类加载器? 定义类加载 ...

  3. JVM的艺术—类加载器篇(三)

    JVM的艺术-类加载器篇(三) 引言 今天我们继续来深入的剖析类加载器的内容.上篇文章我们讲解了类加载器的双亲委托模型.全盘委托机制.以及类加载器双亲委托模型的优点.缺点等内容,没看过的小伙伴请加关注 ...

  4. Java虚拟机笔记 – JVM 自定义的类加载器的实现和使用2

    1.用户自定义的类加载器: 要创建用户自己的类加载器,只需要扩展java.lang.ClassLoader类,然后覆盖它的findClass(String name)方法即可,该方法根据参数指定类的名 ...

  5. 【JVM学习笔记】类加载器

    概述 类加载器用来把类加载到Java虚拟机中.从JDK1.2版本开始,类的加载过程采用父委托机制,这种机制能更好地保证Java平台的安全.在此委托机制中,除了Java虚拟机自带的根类加载器以外,其余的 ...

  6. JVM学习--(六)类加载器原理

    我们知道我们编写的java代码,会经过编译器编译成字节码文件(class文件),再把字节码文件装载到JVM中,映射到各个内存区域中,我们的程序就可以在内存中运行了.那么字节码文件是怎样装载到JVM中的 ...

  7. JVM启动过程 类加载器

    下图来自:http://blog.csdn.net/jiangwei0910410003/article/details/17733153 package com.test.jvm.common; i ...

  8. JVM学习记录-类加载器

    前言 JVM设计团队把类加载阶段中的“通过一个类的全限定名来获取描述此类的二进制字节流”这个动作放到Java虚拟机外面去实现,以便让应用程序自己决定如何去获取所需要的类.实现这个动作的代码模块称为“类 ...

  9. 黑马程序员_java基础笔记(13)...类加载器和代理

    —————————— ASP.Net+Android+IOS开发..Net培训.期待与您交流! —————————— 1,类加载器.2,代理. 1,类加载器. Java虚拟机中可以安装多个类加载器,系 ...

  10. 【深入理解JVM】:类加载器与双亲委派模型

    类加载器 加载类的开放性 类加载器(ClassLoader)是Java语言的一项创新,也是Java流行的一个重要原因.在类加载的第一阶段“加载”过程中,需要通过一个类的全限定名来获取定义此类的二进制字 ...

随机推荐

  1. [转] acmer必看的26个对acm态度

    acmer必看的26个对acm态度   转载自:http://www.cppblog.com/Darren/archive/2009/08/03/92099.html Attempt Keep on ...

  2. hunnu---11547 你的组合数学学得如何?

    解析:比较简单的DP,从左向右一个一个连续着放,dp[X][Y]表示到第X个硬币的时候Y状态的方案数,Y=0表示x左边那个不是正面的,Y=1表示x左边那个是正面 如果左边不是正面,那么当前放正面的就把 ...

  3. jps 显示process information unavailable解决方法

    jps 显示process information unavailable解决办法jps时出现如下信息: 4791 -- process information unavailable 解决办法: 进 ...

  4. java基础全套

    这是我自己早前听课时整理的java基础全套知识  使用于初学者 也可以适用于中级的程序员 我做成了chm文档的类型  你们可以下载  笔记是比较系统全面,可以抵得上市场上90%的学习资料.讨厌那些随便 ...

  5. 关于在SharePoint2013中弹出模态化窗口的问题及关闭事件。

    js: /*弹出对话框方法开始*//** 弹窗方法(需要回传值时,自定义回调方法)* @url: 弹出窗口页面url* @width: 宽度* @height: 高度* @callback: 回调函数 ...

  6. javascript对象几种创建方式

    Javascript对象创建的几种方式  1.使用new运算符创建Object  var box=new Object();  box.name='肖能武';  box.age=28;    2.ne ...

  7. 让SecureCRT vi中显示多色彩

    方法1: 1.打开SecureCRT___选项__会话选项___仿真 右边的中端选择linux,把ANSI 颜色和使用颜色方案打上钩. 2.选择外观,右边的使用颜色和闪烁都打上钩. 3.重新连接使所该 ...

  8. 问题-[Delphi]通过Map文件查找内存地址出错代码所在行

     一 什么是MAP文件       什么是 MAP 文件?简单地讲, MAP 文件是程序的全局符号.源文件和代码行号信息的唯一的文本表示方法,它可以在任何地方.任何时候使用,不需要有额外的程序进行支持 ...

  9. Extending JavaScript Natives

    Most built-in JavaScript types are constructors whose prototypes contain the methods and other prope ...

  10. hdoj 3861 The King’s Problem【强连通缩点建图&&最小路径覆盖】

    The King’s Problem Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 65536/32768 K (Java/Other ...