?
(1)jvm的装载过程以及装载原理
所谓装载就是寻找一个类或是一个接口的二进制形式并用该二进制形式来构造代表这个类或是这个接口的class对象的过程,
其中类或接口的名称是给定了的。当然名称也可以通过计算得到,但是更常见的是通过搜索源代码经过编译器编译后所得到
的二进制形式来构造。 在Java中,类装载器把一个类装入Java虚拟机中,要经过三个步骤来完成:装载、链接和初始化,
其中链接又可以分成校验、准备和解析三步,除了解析外,其它步骤是严格按照顺序完成的,各个步骤的主要工作如下:
装载:查找和导入类或接口的二进制数据;
链接:执行下面的校验、准备和解析步骤,其中解析步骤是可以选择的;
校验:检查导入类或接口的二进制数据的正确性;
准备:给类的静态变量分配并初始化存储空间;
解析:将符号引用转成直接引用;
初始化:激活类的静态变量的初始化Java代码和静态Java代码块。
(2):java中的类是什么?
一个类代表要执行的代码,而数据则表示其相关状态。状态时常改变,而代码则不会。当我们将一个特定的状态与 一个类相对应起来,也就意味着将一个类事例化。尽管相同的类对应的实例其状态千差万别,但其本质都对应着同一段代码。在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的类也有一个具体的标识,我们接下来看。
在Java 中,一个类用其完全匹配类名(fully qualified class name)作为标识,这里指的完全匹配类名包括包名和类名。但在JVM中一个类用其全名和一个加载类ClassLoader的实例作为唯一标识。因此,如 果一个名为Pg的包中,有一个名为Cl的类,被类加载器KlassLoader的一个实例kl1加载,Cl的实例,即C1.class在JVM中表示为 (Cl, Pg, kl1)。这意味着两个类加载器的实例(Cl, Pg, kl1) 和 (Cl, Pg, kl2)是不同的,被它们所加载的类也因此完全不同,互不兼容的。那么在JVM中到底有多少种类加载器的实例?下一节我们揭示答案。
(3):java的几种ClassLoader:
在java中,我们可以取得这么以下三个ClassLoader类:
一.??? ClassLoader基本概念
1.ClassLoader分类
类装载器是用来把类(class)装载进JVM的。
JVM规范定义了两种类型的类装载器:启动内装载器(bootstrap)和用户自定义装载器(user-defined class loader)。
JVM在运行时会产生三个ClassLoader:Bootstrap ClassLoader、Extension ClassLoader和AppClassLoader。Bootstrap是用C++编写的,我们在Java中看不到它,是null,是JVM自带的类 装载器,用来装载核心类库,如java.lang.*等。
AppClassLoader的Parent是ExtClassLoader,而ExtClassLoader的Parent为Bootstrap ClassLoader。
?
Java提供了抽象类ClassLoader,所有用户自定义类装载器都实例化自ClassLoader的子类。 System Class Loader是一个特殊的用户自定义类装载器,由JVM的实现者提供,在编程者不特别指定装载器的情况下默认装载用户类。系统类装载器可以通过 ClassLoader.getSystemClassLoader() 方法得到。
?
?
例1,测试你所使用的JVM的ClassLoader
/*LoaderSample1.java*/
public?? class? LoaderSample1 {
???? public?? static?? void? main(String[] args) {
??????? Class c;
??????? ClassLoader cl;
??????? cl? =? ClassLoader.getSystemClassLoader();
??????? System.out.println(cl);
???????? while? (cl? !=?? null ) {
??????????? cl? =? cl.getParent();
??????????? System.out.println(cl);
??????? }
???????? try? {
??????????? c? =? Class.forName( " java.lang.Object " );
??????????? cl? =? c.getClassLoader();
??????????? System.out.println( " java.lang.Object‘s loader is? "?? +? cl);
??????????? c? =? Class.forName( " LoaderSample1 " );
??????????? cl? =? c.getClassLoader();
??????????? System.out.println( " LoaderSample1‘s loader is? "?? +? cl);
??????? }? catch? (Exception e) {
??????????? e.printStackTrace();
??????? }
??? }
}
在我的机器上(Sun Java 1.4.2)的运行结果
sun.misc.Launcher$AppClassLoader@1a0c10f
sun.misc.Launcher$ExtClassLoader@e2eec8
null
java.lang.Object‘s loader is null
LoaderSample1‘s loader is sun.misc.Launcher$AppClassLoader@1a0c10f
第一行表示,系统类装载器实例化自类sun.misc.Launcher$AppClassLoader
第二行表示,系统类装载器的parent实例化自类sun.misc.Launcher$ExtClassLoader
第三行表示,系统类装载器parent的parent为bootstrap
第四行表示,核心类java.lang.Object是由bootstrap装载的
第五行表示,用户类LoaderSample1是由系统类装载器装载的
注意,我们清晰的看见这个三个ClassLoader类之间的父子关系(不是继承关系),父子关系在ClassLoader的实现中有一个 ClassLoader类型的属性,我们可以在自己实现自定义的ClassLoader的时候初始化定义,而这三个系统定义的ClassLoader的父 子关系分别是
AppClassLoader——————》(Parent)ExtClassLoader——————————》(parent)BootClassLoader(null c++实现)
系统为什么要分别指定这么多的ClassLoader类呢?
答案在于因为java是动态加载类的,这样的话,可以节省内存,用到什么加载什么,就是这个道理,然而系统在运行的时候并不知道我们这个应用与需要加载些什么类,那么,就采用这种逐级加载的方式
(1)首先加载核心API,让系统最基本的运行起来
(2)加载扩展类
(3)加载用户自定义的类
package org.corey.clsloader;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import sun.net.spi.nameservice.dns.DNSNameService;
public class ClsLoaderDemo {
?/**
? * @param args
? */
?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"));
?}
}
程序结果为:
E:\MyEclipse 6.0\jre\lib\rt.jar;E:\MyEclipse 6.0\jre\lib\i18n.jar;E:\MyEclipse 6.0\jre\lib\sunrsasign.jar;E:\MyEclipse 6.0\jre\lib\jsse.jar;E:\MyEclipse 6.0\jre\lib\jce.jar;E:\MyEclipse 6.0\jre\lib\charsets.jar;E:\MyEclipse 6.0\jre\classes
E:\MyEclipse 6.0\jre\lib\ext
E:\workspace\ClassLoaderDemo\bin
在上面的结果中,你可以清晰看见三个ClassLoader分别加载类的路径;也知道为什么我们在编写程序的时候,要把用到的jar包放在工程的 classpath下面啦,也知道我们为什么可以不加载java.lang.*包啦!其中java.lang.*就在rt.jar包中;
(4)ClassLoader的加载机制:
现在我们设计这种一下Demo:
package java.net;
public class URL {
?private String path;
?public URL(String path) {
??this.path = path;
?}
?public String toString() {
??return this.path + " new Path";
?}
}
package java.net;
import java.net.*;
public class TheSameClsDemo {
?/**
? * @param args
? */
?public static void main(String[] args) {
??URL url = new URL("http://www.baidu.com ");
??System.out.println(url.toString());
?}
}
在这种情况下,系统会提示我们出现异常,因为我们有两个相同的类,一个是真正的URL,一个是我在上面实现的伪类;出现异常是正常的,因 为你想想,如果我们在执行一个applet的时候,程序自己实现了一个String的类覆盖了我们虚拟机上面的真正的String类,那么在这个 String里面,不怀好意的人可以任意的实现一些功能;这就造成极不安全的隐患;所以java采用了一种名为“双亲委托”的加载模式;
以下是jdk源代码:
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 = findBootstrapClass0(name);
??}
???? } catch (ClassNotFoundException e) {
???????? // If still not found, then invoke findClass in order
???????? // to find the class.
???????? c = findClass(name);
???? }
?}
?if (resolve) {
???? resolveClass(c);
?}
?return c;
??? }
在上面的代码中,我们可以清晰的看见,我们调用一个ClassLoader加载程序的时候,这个ClassLoader会先调用设置好的 parent ClassLoader来加载这个类,如果parent是null的话,则默认为Boot ClassLoader类,只有在parent没有找的情况下,自己才会加载,这就避免我们重写一些系统类,来破坏系统的安全;
再来看一个明显的例子:
package org.corey;
public class MyCls{
?private String name;
?
?public MyCls(){
?
?}
?public MyCls(String name){
?this.name=name;
?}
?
?public void say(){
?System.out.println(this.name);
?}
}
把上面这个MyCls类打成jar包,丢进ext classLoader的加载路径;
然后写出main类:
package org.corey.clsloader;
import org.corey.MyCls;
public class TheSameClsDemo {
?/**
? * @param args
? */
?public static void main(String[] args) {
??MyCls myClsOb=new MyCls("name");
???? myClsOb.say();?
???? System.out.println(MyCls.class.getClassLoader());
???? System.out.println(System.getProperty("java.class.path"));
???? System.out.println(TheSameClsDemo.class.getClassLoader());
?}
}
并且把MyCls类加入biild-path里面方便引用;
结果是:
name
sun.misc.Launcher$ExtClassLoader@16930e2
E:\workspace\ClassLoaderDemo\bin;E:\MyEclipse 6.0\jre\lib\ext\corey.jar
sun.misc.Launcher$AppClassLoader@7259da
从上面的例子可以清晰的看出ClassLoader之间的这种双亲委托加载模式;
?