JVM的ClassLoader过程分析
本文来自网络:深入分析Java ClassLoader原理
http://my.oschina.net/zhengjian/blog/133836
一、 JVM的ClassLoader过程以及装载原理
ClassLoader就是寻找类或是接口的字节码文件(.class)并通过解析字节码文件来构造类或接口对象的过程。在Java中,类装载器把一个类装入Java虚拟机中,要经过三个步骤来完成:寻找文件、链接和初始化,其中链接又可以分成校验、准备和解析三步,除了解析外,其它步骤是严格按照顺序完成的,各个步骤的主要工作如下:
导入class字节码:查找class文件,导入class或者接口的字节码;
链接:执行下面的校验、准备和解析步骤,其中解析步骤是可以选择的;
校验:检查导入类或接口的二进制数据的正确性;
准备:给类的静态变量分配并初始化存储空间;
解析:将符号引用转成直接引用;
初始化:激活类的静态变量的初始化Java代码和静态Java代码块。
二、classpath的作用:查找class文件
classpath的作用是指定查找类的路径:当使用java命令执行一个类(类中的main方法)时,会从classpath中进行查找这个类。
设置classpath方式一:
设置环境变量CLASSPATH,多个路径之间使用英文的分号隔开,也可以指定为jar包路径。
示例:CLASSPATH=c:/myclasses/;c/mylib/aa.jar;c:/mylib/bb.jar;.
注意:在Windows中不区分大小写,所以指定的环境变量名为classpath或是ClassPath都一样。
设置classpath方式二:
执行java命令时通过-classpath参数指定。
示例:java -classpath c:/myclasses/;c:/mylib/aa.jar cn.itcast.MainApp
注意:这样就只用这个参数指定的classpath,找不到类就报错,不会使用CLASSPATH环境变量!
结论:按classpath中指定的顺序,先从前面的路径中查找,如果找不到,在从下一个路径中查找,直到找到类字节码或是报NoClassDefFoundError。
另外一种指定class路径方式:将class类字节码文件打成jar包,并放到JRE的lib/ext/目录下,这样在执行时就可以直接找到这个类而不需要指定classpath。
如何将class类打包为jar文件?java的安装目录下面bin目录下有一个工具:jar.exe、eclipse 自带的export 和Ant, Maven之类的构建都可以轻松打包。
命令:jar cvf jar包的名字.jar 文件 文件 文件
例如:jar cvf a.jar HelloWorld.class Test.class
怎样打开jar包呢?
使用一般的解压工具就可以了。
问题如何使用java的工具执行MyEclipseKeyGen.jar呢?
比如MyEclipse6.0的注册机就是一个jar文件。
操作如下:java -jar MyEclipseKeyGen.jar。
三、Java环境中ClassLoader
java应用环境中不同的class分别由不同的ClassLoader负责加载。一个jvm中默认的classloader有Bootstrap ClassLoader、Extension ClassLoader和App ClassLoader:
1) Bootstrap ClassLoader称启动类加载器,是Java类加载层次中最顶层的类加载器,负责加载JDK中的核心类库,主要是 %JRE_HOME/lib/ 目录下的rt.jar、resources.jar、charsets.jar,可通过如下程序获得该类加载器从哪些地方加载了相关的jar或class文件。Bootstrap ClassLoader不继承自ClassLoader,因为它不是一个普通的Java类,底层由C++编写,已嵌入到了JVM内核当中,当JVM启动后,Bootstrap ClassLoader也随着启动,负责加载完核心类库后,并构造Extension ClassLoader和App ClassLoader类加载器。可以通过sun.boot.class.path系统属性查询本机JDK环境:
System.out.println(System.getProperty("sun.boot.class.path"));
2) Extension ClassLoader 称为扩展类加载器,负责加载Java的扩展类库,默认加载JAVA_HOME/jre/lib/ext/目下的所有jar。
3) App ClassLoader系统类加载器,负责加载当前java应用的classpath中的所有类。
4) 自定义ClassLoader(),自定义的ClassLoader都必须继承自抽象类java.lang.ClassLoader,重写findClass方法,这个方法定义了ClassLoader查找class的方式。
主要可以扩展的方法有:
findClass 定义查找Class的方式
defineClass 将类文件字节码加载为jvm中的class
findResource 定义查找资源的方式
可以直接使用或继承已有的ClassLoader实现,比如java.net.URLClassLoader、java.security.SecureClassLoader、 java.rmi.server.RMIClassLoader。
Extension ClassLoader 和 App ClassLoader都是java.net.URLClassLoader的子类。
这个是URLClassLoader的构造方法:
public URLClassLoader(URL[] urls, ClassLoader parent)
public URLClassLoader(URL[] urls)urls参数是需要加载的ClassPath url数组,可以指定parent ClassLoader,不指定的话默认以当前调用类的ClassLoader为parent。
代码如下:
ClassLoader classLoader = new URLClassLoader(urls);
Thread.currentThread().setContextClassLoader(classLoader);
Class clazz=classLoader.loadClass("com.company.MyClass");//使用loadClass方法加载class,这个class是在urls参数指定的classpath下边。
Method taskMethod = clazz.getMethod("doTask", String.class, String.class);//然后我们就可以用反射做些事情了
taskMethod.invoke(clazz.newInstance(),"hello","world");
四、ClassLoader加载类的原理
1、 原理介绍
ClassLoader使用的是双亲委托模型来搜索类的,每个ClassLoader实例都有一个父类加载器的引用(不是继承的关系,是一个包含的关系),虚拟机内置的类加载器(Bootstrap ClassLoader)本身没有父类加载器,但可以用作其它ClassLoader实例的的父类加载器。当一个ClassLoader实例需要加载某个类时,它会试图亲自搜索某个类之前,先把这个任务委托给它的父类加载器,这个过程是由上至下依次检查的,首先由最顶层的类加载器Bootstrap ClassLoader试图加载,如果没加载到,则把任务转交给Extension ClassLoader试图加载,如果也没加载到,则转交给App ClassLoader 进行加载,如果它也没有加载得到的话,则返回给委托的发起者,由它到指定的文件系统或网络等URL中加载该类。如果它们都没有加载到这个类时,则抛出ClassNotFoundException异常。否则将这个找到的类生成一个类的定义,并将它加载到内存当中,最后返回这个类在内存中的Class实例对象。
2、 为什么要使用双亲委托这种模型呢?
因为这样可以避免重复加载。当父亲已经加载了该类的时候,就没有必要子ClassLoader再加载一次。考虑到安全因素,我们试想一下,如果不使用这种委托模式,那我们就可以随时使用自定义的String来动态替代java核心api中定义的类型,这样会存在非常大的安全隐患,而双亲委托的方式,就可以避免这种情况,因为String已经在启动时就被引导类加载器(Bootstrcp ClassLoader)加载,所以用户自定义的ClassLoader永远也无法加载一个自己写的String,除非你改变JDK中ClassLoader搜索类的默认算法。
3、 JVM在搜索类的时候,又是如何判定两个class是相同的呢?
JVM在判定两个class是否相同时,不仅要判断两个类名是否相同,而且要判断是否由同一个类加载器实例加载的。只有两者同时满足的情况下,JVM才认为这两个class是相同的。就算两个class是同一份class字节码,如果被两个不同的ClassLoader实例所加载,JVM也会认为它们是两个不同class。比如网络上的一个Java类org.classloader.simple.NetClassLoaderSimple,javac编译之后生成字节码文件NetClassLoaderSimple.class,ClassLoaderA和ClassLoaderB这两个类加载器并读取了NetClassLoaderSimple.class文件,并分别定义出了java.lang.Class实例来表示这个类,对于JVM来说,它们是两个不同的实例对象,但它们确实是同一份字节码文件,如果试图将这个Class实例生成具体的对象进行转换时,就会抛运行时异常java.lang.ClassCaseException,提示这是两个不同的类型。

五、ClassLoader加载类的流程
Bootstrap ClassLoader是JVM级别的,由C++撰写;Extension ClassLoader、App ClassLoader都是java类,都继承自URLClassLoader超类。Bootstrap ClassLoader由JVM启动,然后初始化sun.misc.Launcher ,sun.misc.Launcher初始化Extension ClassLoader、App ClassLoader。

在JAVA中,一个类通常有着一个.class文件,但也有例外。在JAVA运行时环境中(Java runtime),每一个类都有一个以第一类(first-class)的Java对象所表现出现的代码,其是java.lang.Class的实例。编译一个JAVA文件,编译器都会嵌入一个public, static, final修饰的类型为java.lang.Class,名称为class的域变量在其字节码文件中。因为使用了public修饰,可以采用如下的形式对其访问:
java.lang.Class klass = Myclass.class;
一旦一个类被载入JVM中,同一个类就不会被再次载入了(切记,同一个类)。这里存在一个问题就是什么是“同一个类”?正如一个对象有一个具体的状态,即标识,一个对象始终和其代码(类)相关联。同理,载入JVM的类也有一个具体的标识,一个类用其完全匹配类名(fully qualified class name)作为标识,这里指的完全匹配类名包括包名和类名。但在JVM中一个类用其全名和一个加载类ClassLoader的实例作为唯一标识。因此,如果一个名为Pg的包中,有一个名为Cl的类,被类加载器KlassLoader的一个实例kl1加载,Cl的实例,即C1.class在JVM中表示为(Cl, Pg, kl1)。这意味着两个类加载器的实例(Cl, Pg, kl1) 和 (Cl, Pg, kl2)是不同的,被它们所加载的类也因此完全不同,互不兼容的。
例1,测试你所使用的JVM的ClassLoader
import java.net.URL;
public class Sample {
public static void main(String[] args)
{
ClassLoader cloader;
cloader = ClassLoader.getSystemClassLoader();
System.out.println(cloader);
while (cloader != null )
{
cloader = cloader.getParent();
System.out.println(cloader);
}
try
{
Class<Object> c1 = (Class<Object>)Class.forName("java.lang.Object" );
cloader = c1.getClassLoader();
System.out.println( "java.lang.Object's loader is " + cloader);
Class<Sample> c2 = (Class<Sample>) Class.forName("Sample" );
cloader = c2.getClassLoader();
System.out.println( " Sample's loader is " + cloader);
}
catch (Exception e)
{
e.printStackTrace();
}
}
}
在我的机器上( Java 1.7.2)的运行结果
sun.misc.Launcher$AppClassLoader@4d905742
sun.misc.Launcher$ExtClassLoader@3f50d5d6
null
java.lang.Object's loader is null
Sample's loader is sun.misc.Launcher$AppClassLoader@4d905742
第一行表示,系统类装载器实例化自类sun.misc.Launcher$AppClassLoader
第二行表示,系统类装载器的parent实例化自类sun.misc.Launcher$ExtClassLoader
第三行表示,系统类装载器parent的parent为bootstrap
第四行表示,核心类java.lang.Object是由bootstrap装载的
第五行表示,用户类Sample是由系统类装载器装载的
注意,我们清晰的看见这个三个ClassLoader类之间的父子关系(不是继承关系),父子关系在ClassLoader的实现中有一个ClassLoader类型的属性,我们可以在自己实现自定义的ClassLoader的时候初始化定义,而这三个系统定义的ClassLoader的父子关系分别是
AppClassLoader——————》(Parent)ExtClassLoader——————————》(parent)BootClassLoader(null c++实现)
系统为什么要分别指定这么多的ClassLoader类呢?
答案在于因为java是动态加载类的,这样的话,可以节省内存,用到什么加载什么,就是这个道理,然而系统在运行的时候并不知道我们这个应用与需要加载些什么类,那么,就采用这种逐级加载的方式
(1)首先加载核心API,让系统最基本的运行起来
(2)加载扩展类 (3)加载用户自定义的类
import java.net.URL;
public class Sample {
public static void main(String[] args)
{
System.out.println(System.getProperty("sun.boot.class.path"));
System.out.println(System.getProperty("java.ext.dirs"));
System.out.println(System.getProperty("java.class.path"));
}
}
在上面的结果中,你可以清晰看见三个ClassLoader分别加载类的路径;也知道为什么我们在编写程序的时候,要把用到的jar包放在工程的classpath下面啦,也知道我们为什么可以不加载java.lang.*包啦!其中java.lang.*就在rt.jar包中。
六 总结:
classloader有Bootstrap ClassLoader(JVM默认 C++编写)、Extension ClassLoader和App ClassLoader
也可有自定义classloader。
通过classloader 装载类的流程:
导入class字节码:查找class文件,导入class或者接口的字节码;
链接:执行下面的校验、准备和解析步骤,其中解析步骤是可以选择的;
校验:检查导入类或接口的二进制数据的正确性;
准备:给类的静态变量分配并初始化存储空间;
解析:将符号引用转成直接引用;
初始化:激活类的静态变量的初始化Java代码和静态Java代码块。
JVM的ClassLoader过程分析的更多相关文章
- java虚拟机学习-慢慢琢磨JVM(2-1)ClassLoader的工作机制
ClassLoader的工作机制 java应用环境中不同的class分别由不同的ClassLoader负责加载. 一个jvm中默认的classloader有Bootstrap ClassLoader. ...
- JVM思考-ClassLoader.loadClasshe和Class.forName区别
JVM思考-ClassLoader.loadClasshe和Class.forName区别 目录:JVM总括:目录 见博客第四节:JVM总括四-类加载过程.双亲委派模型.对象实例化过程
- JVM和ClassLoader
JVM和ClassLoader 2019-11-08 目录 1 JVM架构整体架构 1.1 类加载器子系统 1.1.1 加载 1.1.2 链接 1.1.3 初始化 1.2 运行时数据区(Runtime ...
- JVM的classloader(转)
Java中一共有四个类加载器,之所以叫类加载器,是程序要用到某个类的时候,要用类加载器载入内存. 这四个类加载器分别为:Bootstrap ClassLoader.Extension Class ...
- 关于JVM的ClassLoader(转)
众所周知,java是编译型的语言,写的是java文件,最后运行的是class文件,class文件是运行在JVM之中的,这时候就有一个问题,JVM如何装载class文件的?是通过ClassLoader来 ...
- JVM 系列 ClassLoader
JVM 系列()ClassLoader 在前面一节中,主要介绍了 Class 的装载过程,Class 的装载大体上可以分为加载类.连接类和初始化 3 个阶段.本小节将主要介绍绍 Java 语言中的 C ...
- JVM中ClassLoader的学习
JVM中class loaderの学习 一..class文件和jvm的关系 类的加载 所有的编译生成的.class文件都会被直接加载到JVM里面来吗(并不 首先我们明确一个概念,.class文件加载到 ...
- jvm学习-ClassLoader(二)
ClassLoader结构 jdk加载的4个步骤 CustomClassLoader 用户自定义的classLoader APPClassLoader主要加载classPath下面的class Ext ...
- JVM(三)JVM的ClassLoader类加载器
1.类加载的生命周期 类从被加载到虚拟机内存中开始,到卸载出内存为止,它的整个生命周期包括: (1)加载 (2)验证 (3)准备 (4)解析 (5)初始化 (6)使用 (7)卸载 一共7个阶段.其中验 ...
随机推荐
- iOS7新JavaScriptCore框架入门介绍
前阵子,Apple正式发布了新的iOS 7系统,最大最直观的改变在于界面变得小清新范了,我也提到<iOS,你真的越来越像Android了>.不过对于移动开发者来说,除了要适应Xcode 5 ...
- [leetcode]Second Highest Salary
找第二大 # Write your MySQL query statement below SELECT MAX(Salary) FROM Employee WHERE Salary NOT IN ( ...
- C# 类型基础——你可能忽略的技术细节
引言 本文之初的目的是讲述设计模式中的 Prototype(原型)模式,但是如果想较清楚地弄明白这个模式,需要了解对象克隆(Object Clone),Clone 其实也就是对象复制.复制又分为了浅度 ...
- Query Object--查询对象模式(下)
回顾 上一篇对模式进行了介绍,并基于ADO.NET进行了实现,虽然现在ORM框架越来越流行,但是很多中小型的公司仍然是使用ADO.NET来进行数据库操作的,随着项目的需求不断增加,业务不断变化,ADO ...
- golang 图片处理,剪切,base64数据转换,文件存储
本文主要介绍: 1. 图片文件的读写. 2. 图片在go缓存中如何与base64互相转换 3. 图片裁剪 本文中,为了方便查看,去掉所有错误判断 base64 -> file ddd, _ := ...
- QWidget::paintEngine: Should no longer be called
Qt新手,其实并不知道出现这个问题的本质原因,我的问题在于paintEvent中使用的painter是类的成员而不是临时新建的局部变量,改为使用局部变量问题就消失了.
- .NET面试必备(整理)
1.简述 private. protected. public. internal 修饰符的访问权限. private : 私有成员, 在类的内部才可以访问.public : 公共成员,完全公开,没有 ...
- JS文件放在头还是尾
目前绝大部分的浏览器都是采取阻塞方式(Scripts Block Downloads)加载Javascript文件的:javascript在头部会阻止其他元素并行加载(css,图片,网页):这种机制的 ...
- 审核流(3)低调奢华,简单不凡,实例演示-SNF.WorkFlow--SNF快速开发平台3.1
下面我们就从什么都没有,结合审核流进行演示实例.从无到有如何快速完美的实现,然而如此简单.低调而奢华,简单而不凡. 从只有数据表通过SNF.CodeGenerator代码生成器快速生成单据并与审核流进 ...
- Lua中的weak表——weak table
弱表(weak table)是一个很有意思的东西,像C++/Java等语言是没有的.弱表的定义是:A weak table is a table whose elements are weak ref ...