虚拟机设计团队把类加载阶段中的“通过一个类的全限定名来获取描述此类的二进制字节流”这个动作放到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. VJP1456 最小总代价(状压)

    链接 这题卡了一天  刚开始没考虑第一个传的和最后一个传的 感觉挺简单 啪啪的敲完 居然还过了17组数据.. 正解:dp数组一维保存状态 一维保存当前传到了谁 再枚举是由谁传过来的 这样可以保证正确性 ...

  2. BZOJ_1009_[HNOI2008]_GT考试_(动态规划+kmp+矩阵乘法优化+快速幂)

    描述 http://www.lydsy.com/JudgeOnline/problem.php?id=1009 字符串全部由0~9组成,给出一个串s,求一个长度为n的串,不包含s的种类有多少. 分析 ...

  3. 白书P61 - 点集配对问题

    白书P61 - 点集配对问题 状压DP #include <iostream> #include <cstdio> #include <cstring> using ...

  4. .Net 垃圾回收机制原理(一)

    英文原文:Jeffrey Richter 编译:赵玉开 链接:http://www.cnblogs.com/yukaizhao/archive/2011/11/23/dot_net_GC_1.html ...

  5. linq中的contains条件

    linq中的contains条件   在sql查询语句中,in 在linq 中用contains,并且contains前面是数组,而后面是列名,如: SELECT distinct BH FROM c ...

  6. C# using Sendkey function to send a key to another application

    If notepad is already started, you should write: // import the function in your class [DllImport (&q ...

  7. NHibernate统一类封装代码

    NHibernate已经成为.net主流的ORM框架,当然,在开发中如果需要使用NHibernate的话,我们一般会对她进行一次封装,以便在项目中使用更方便,以及对NHibernate有一个全局的控制 ...

  8. NodeJS 框架 Express 从 3.0升级至4.0的新特性

    NodeJS 框架 Express 从 3.0升级至4.0的新特性 [原文地址:√https://scotch.io/bar-talk/expressjs-4-0-new-features-and-u ...

  9. NOIP2001 Car的旅行路线

    题四 Car的旅行路线(30分) 问题描述 又到暑假了,住在城市A的Car想和朋友一起去城市B旅游.她知道每个城市都有四个飞机场,分别位于一个矩形的四个顶点上,同一个城市中两个机场之间有一条笔直的高速 ...

  10. NOIP2001 一元三次方程求解

    题一  一元三次方程求解(20分) 问题描述 有形如:ax3+bx2+cx+d=0  这样的一个一元三次方程.给出该方程中各项的系数(a,b,c,d  均为实数),并约定该方程存在三个不同实根(根的范 ...