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

    1797: [Ahoi2009]Mincut 最小割 Time Limit: 10 Sec  Memory Limit: 162 MBSubmit: 1072  Solved: 446[Submit] ...

  2. wildfly9 配置SSL单向认证/https

    D:\>keytool -genkey -keystore cdi-init.keystore -alias cdi-init -keyalg RSA -keysize 2048 -validi ...

  3. 《C#并行编程高级教程》第4章 并发集合 笔记

    这一章主要介绍了System.Collections.Concurrent下的几个类. ConcurrentQueue<T> 并发队列.完全无锁,使用CAS(compare-and-swa ...

  4. windows log

    http://technet.microsoft.com/zh-CN/sysinternals http://technet.microsoft.com/en-us/sysinternals/bb89 ...

  5. Linux下的iwpriv(iwlist、iwconfig)的简单应用

    无线网络飞速发展的今天,许多设备都提供了连接无线网络的功能. 那么Linux下的wifi到底该怎么配置.连接呢?? 开始配置之前,我们要说说iw家族.iw是linux下常用的wifi配置工具,网上有相 ...

  6. Python ImportError: No module named *****

    如果想使用非当前模块中的代码,需要使用Import,这个大家都知道. 如果你要使用的模块(py文件)和当前模块在同一目录,只要import相应的文件名就好,比如在a.py中使用b.py: import ...

  7. shell color

    shell 输出着色 格式: echo "/033[字背景颜色;字体颜色m字符串/033[控制码" 如果单纯显示字体颜色可以固定控制码位0m. 格式: echo "/03 ...

  8. 【JS】Intermediate4:JSON

    1. JSON(JavaScript Object Notation) A set of text formatting rules for storing and transferring data ...

  9. NOIP2002 矩形覆盖

    题四 矩形覆盖(存盘名NOIPG4) [问题描述]: 在平面上有 n 个点(n <= 50),每个点用一对整数坐标表示.例如:当 n=4 时,4个点的坐标分另为:p1(1,1),p2(2,2), ...

  10. HW5.20

    public class Solution { public static void main(String[] args) { System.out.printf("%s\t%s\t%s\ ...