本文来自网络:深入分析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过程分析的更多相关文章

  1. java虚拟机学习-慢慢琢磨JVM(2-1)ClassLoader的工作机制

    ClassLoader的工作机制 java应用环境中不同的class分别由不同的ClassLoader负责加载. 一个jvm中默认的classloader有Bootstrap ClassLoader. ...

  2. JVM思考-ClassLoader.loadClasshe和Class.forName区别

    JVM思考-ClassLoader.loadClasshe和Class.forName区别 目录:JVM总括:目录 见博客第四节:JVM总括四-类加载过程.双亲委派模型.对象实例化过程

  3. JVM和ClassLoader

    JVM和ClassLoader 2019-11-08 目录 1 JVM架构整体架构 1.1 类加载器子系统 1.1.1 加载 1.1.2 链接 1.1.3 初始化 1.2 运行时数据区(Runtime ...

  4. JVM的classloader(转)

    Java中一共有四个类加载器,之所以叫类加载器,是程序要用到某个类的时候,要用类加载器载入内存.    这四个类加载器分别为:Bootstrap ClassLoader.Extension Class ...

  5. 关于JVM的ClassLoader(转)

    众所周知,java是编译型的语言,写的是java文件,最后运行的是class文件,class文件是运行在JVM之中的,这时候就有一个问题,JVM如何装载class文件的?是通过ClassLoader来 ...

  6. JVM 系列 ClassLoader

    JVM 系列()ClassLoader 在前面一节中,主要介绍了 Class 的装载过程,Class 的装载大体上可以分为加载类.连接类和初始化 3 个阶段.本小节将主要介绍绍 Java 语言中的 C ...

  7. JVM中ClassLoader的学习

    JVM中class loaderの学习 一..class文件和jvm的关系 类的加载 所有的编译生成的.class文件都会被直接加载到JVM里面来吗(并不 首先我们明确一个概念,.class文件加载到 ...

  8. jvm学习-ClassLoader(二)

    ClassLoader结构 jdk加载的4个步骤 CustomClassLoader 用户自定义的classLoader APPClassLoader主要加载classPath下面的class Ext ...

  9. JVM(三)JVM的ClassLoader类加载器

    1.类加载的生命周期 类从被加载到虚拟机内存中开始,到卸载出内存为止,它的整个生命周期包括: (1)加载 (2)验证 (3)准备 (4)解析 (5)初始化 (6)使用 (7)卸载 一共7个阶段.其中验 ...

随机推荐

  1. 基情四射的两个css样式

    自定义blog样式时,代码段的line-height继承样式post的line-height,间隔太大了,决定再减小点,css都玩了几年了,感觉中这是很容易的事情.然后,就悲剧了好久,原先自定义样式表 ...

  2. 【Android】如何快速构建Android Demo

    [Android]如何快速构建Android Demo 简介 在 Android 学习的过程中,经常需要针对某些项目来写一些测试的例子,或者在做一些 demo 的时候,都需要先写 Activity 然 ...

  3. CLR via C#深解笔记二 - 类型设计

    类型基础 所有类型都从System.Object派生   CLR要求所有对象都用new 操作符来创建. Employee e = new Employee("Constructor Para ...

  4. thinkphp 3.2.3+Admin LTE后台框架

  5. xming + putty 搭建远程图形化ssh访问ubuntu 14.04

    putty下载: http://www.putty.org/ 一般我们远程登录linux 服务器,都是使用非加密的 telnet 或者加密的 ssh.这些登录方式有一个特点:只能登录字符界面,不能运行 ...

  6. 让我们一起Go(九)

    前言: 又好久么更新了,无奈公司项目多,自己又接了私活,于是时间更不够了......不过我是不会让它流产的,坚持! 一.Go语言中的函数 终于轮到函数了,其实也没有什么好说的,无非就是一个语法问题,c ...

  7. JavaScript备忘录(1)——内置类型

    JavaScript有一些内置类型,还有很多常用的内置的方法,本文稍作总结,以备查阅. 值类型 我的理解,值类型是分配在栈上的,而引用类型(当然也包括引用类型内部的值类型)是分配在堆上的.值类型是不可 ...

  8. ruby -- 进阶学习(六) devise修改邮件发送者邮箱

    在config/environment.rb/development.rb或者config/environment/production.rb中, 简单示范例子: Text03::Applicatio ...

  9. ruby -- 进阶学习(八)自定义方法route配置

     在route中进行修改,添加下面代码 namespace :mycontroller do get 'mymethod' , :on=> :member end end 注: :on => ...

  10. TypeScript札记:初体验

    1.简介 TypeScript 是一种由微软开发的自由和开源的编程语言.它是JavaScript的一个超集,而且本质上向这个语言添加了可选的静态类型和基于类的面向对象编程. TypeScript是一种 ...