yangyansong

来源:互联网 发布:炉石传说大数据效率 编辑:程序博客网 时间:2024/04/29 04:08

王森写《java深度歷險》第二章《深入類別載入器》一文对java类加载机制给出了很好的解释,读了以后有个啥印象呢?

预先加载还是按需加载?

什么时候jvm加载程序需要的类呢?

两种情况:

在系统启动的时候全部加载进来 or当程序调用类之前加载

sun的jvm(1.4为例)使用了上面的两种调用方式。

举个例子:

public class main {

public static void main(string[] args) {

new classa().methoda();

}

}

public class classa {

public void methoda(){

system.out.println("use classa");

}

}

java -verbose:class main

用jdk5.0编译的结果

[opened d:\java\jdk1.5\jre\lib\rt.jar](开始加载java基础类所在的包,可以看到jdk1.5就加载了 rt.jar jsse.jar jce.jar charsets.jar 几个jar包)

[opened d:\java\jdk1.5\jre\lib\jsse.jar]

[opened d:\java\jdk1.5\jre\lib\jce.jar]

[opened d:\java\jdk1.5\jre\lib\charsets.jar]

[loaded java.lang.object from d:\java\jdk1.5\jre\lib\rt.jar](开始加载jar包里面的类)

.

.

.

[loaded java.security.cert.certificate from d:\java\jdk1.5\jre\lib\rt.jar]

[loaded com.zte.classloadertest.main from file:/d:/eclipse/workspace3.2.2/classloadertest/bin/](开始加载程序入口类main)

[loaded com.zte.classloadertest.classa from file:/d:/eclipse/workspace3.2.2/classloadertest/bin/](加载类classa)

use classa

[loaded java.lang.shutdown from d:\java\jdk1.5\jre\lib\rt.jar]

[loaded java.lang.shutdown$lock from d:\java\jdk1.5\jre\lib\rt.jar](看看sun的注释governing the virtual-machine shutdown sequence.大家应该明白了吧?)

用jdk6.0编译的结果

[loaded java.lang.object from shared objects file](看看jdk6.0是不是有点不一样啊?没有加载jar包的一步了,据说jdk6.0的jvm速度增加了,猜测是为了速度优化了)

[loaded java.io.serializable from shared objects file]

.

.

.

.

[loaded java.security.principal from shared objects file]

[loaded java.security.cert.certificate from shared objects file]

[loaded com.zte.classloadertest.main from file:/d:/eclipse/workspace3.2.2/classloadertest/bin/](同上)

[loaded com.zte.classloadertest.classa from file:/d:/eclipse/workspace3.2.2/classloadertest/bin/](同上)

use classa

[loaded java.util.abstractlist$itr from shared objects file](从这里开始有点不一样了,这是在干什么?jdk6.0的shutdown吧!呵呵 )

[loaded java.util.identityhashmap$keyset from shared objects file]

[loaded java.util.identityhashmap$identityhashmapiterator from shared objects file]

[loaded java.util.identityhashmap$keyiterator from shared objects file]

[loaded java.io.deleteonexithook from shared objects file]

[loaded java.util.linkedhashset from shared objects file]

[loaded java.util.hashmap$keyset from shared objects file]

[loaded java.util.linkedhashmap$linkedhashiterator from shared objects file]

[loaded java.util.linkedhashmap$keyiterator from shared objects file]

从上面的例子可以很清除的看到在jvm刚启动的时候是使用预先加载(pre-loading)的机制将java基础类的jar包都加载进来。当基础类都加载结束以后,开始加载static main 所在的类。调用static main方法,方法中使用到了类classa 所以先加载classa,然后调用classa的方法。也就是说,当jvm执行完初始操作(加载基础类)以后,对待客户程序使用按需加载(load-on-demand)的方式,当程序需要的时候jvm执行加载操作。

java程序的动态性

java程序是具有动态性的,平时我们使用到java的动态性比较少,new object()的时候我们没有意识到,但是java的动态性开始启动了.......

java有两种方式来达到动态性: 显示的和隐式的

两种方式在底层实现上来说都使用的同样的机制,差异在java程序设计师使用的代码不同。

隐式的(implicit)

当你的java代码中出现new的时候,类的动态加载机制就开始了。

显示的(explicit)

由于隐式的类加载没有弹性,所以sun提供了显示的方式来动态加载类。

显示加载的两个方法:

class的forname方法&classloader的loadclass方法

还是让我们看一个例子吧:

显示动态加载的例子

public interface assembly {

public void run();

}

public class excel implements assembly {

public void run() {

system.out.println("excel is run");

}

}

public class word implements assembly {

public void run() {

system.out.println("word is run");

}

}

public class main {

public static void main(string[] args) throws exception {

class c = class.forname(args[0]);

object o = c.newinstance();

assembly a = (assembly) o;

a.run();

}

}

有了代码就不多说了........

从上面的代码可以看到:

通过class.forname(string s)来获取类的class类型 通过class的newinstance()来加载类,获取实例。

还有一个方法class forname(string s, boolean flag, classloader classloader)s 表示需要加载的类的名称

flag表示加载类的时候是否先初始化类的静态区classloader表示需要用到的类加载器

默认的一个参数的newinstance的flag是true,就是“载入类别+呼叫静态初始化块”

如果使用这种方法的话,需要有一个类加载器 由于classloader.getcallerclassloade()是私有的方法所以不能直接获取类加载器。所以我们使用的时候可以随便找一个类然后获取它的类加载器。

test test = new test();

classloader c1 = test.getclass().getclassloader();

或者

class b = test.class;

classloader c1 = b.getclassloader();

这样就可以获取classloader了。

然后使用class.forname(string s).newinstance() 还是classloader.loadclass()就自便了。

classloader怎么获取是有一些说道的:

要想获取classloader需要先有一个class

在java中每个类别的老祖宗都是object,而object有一个getclass的方法可以得到一个class类别(class.class)的实体。但这个方法是私有的,别想动。那么这个class类别实体从何而来,是在.class载入jvm的时候生成的,以后我们再想得到一个类的实例的时候就需要通过这个class.class代理来与jvm里面的.class来通讯了。

个人胡思乱想: object里面的getclass肯定是交给jvm加载类的时候使用的;使用class.class的时候如果该class已经加载则ok,如果没有加载那么jvm会先加载它。

这个classloader到底是何物,还不是很清楚,还是继续看下去吧!

自定义类加载器来加载类

这里我们使用到了java的java.net.urlclassloader

import java.net.url;

import java.net.urlclassloader;

public class main {

public static void main(string[] args) throws exception {

// 使用默认的newinstance方法

// class c = class.forname(args[0]);

// object o = c.newinstance();

// 使用

// classloader loader = word.class.getclassloader();

// class c = loader.loadclass(args[0]);

// object o = c.newinstance();

// 自定义urlclassloader

url url = new url("file:/d:/eclipse/workspace3.2.2/classloadertest/com");

urlclassloader loader = new urlclassloader(new url[] { url });

class c = loader.loadclass(args[0]);

object o = c.newinstance();

assembly a = (assembly) o;

a.run();

url url2 = new url("file:/d:/eclipse/workspace3.2.2/classloadertest/com");

urlclassloader loader2 = new urlclassloader(new url[] { url2 });

class c2 = loader2.loadclass(args[0]);

object o2 = c2.newinstance();

assembly a2 = (assembly) o2;

a2.run();

}

}

上面的例子按照王森所说的应该是出现同一个类别加载两次的情况,但我屡试不行。暂且记下吧!

按王森所说一个类只能被同一个classloader加载一次,但是可以被不同的classloader同时加载。

jvm中应用类加载器的结构

编译后的.class是如何启动的呢?下面说一下这个过程:

当我们在命令行输入 java xx(.class)的时候,java.exe根据一定的规则找到jre(规则见下文)。接着找到jre之中的jvm.dll(真正的java虚拟机),最后载入这个动态链接库启动java虚拟机。

虚拟机一启动先做一些初始化的动作,比如获取系统参数等。之后就产生第一个类加载器,即所谓的bootstrap loader。bootstrap loader是由c++编写的,这个loader进行了一些初始化操作以后,最重要的是载入定义在sun.misc命名空间下的launcher.java之中的extclassloader(因为是inner class ,所以编译之后变成launcher$extclassloader.class),并设定parent为null,代表其父加载器为bootsrap loader。然后,bootstrap loader再载入launcher.java之中的appclassloader(同理为launcher$appclassloader.class)并设定parent为extclassloader

如上的过程用代码测试一下:

import java.net.url;

import java.net.urlclassloader;

public class main {

public static void main(string[] args) throws exception {

classloader loader = main.class.getclassloader();

system.out.println(loader);

classloader loaderparent = loader.getparent();

system.out.println(loaderparent);

classloader loaderparentparent = loaderparent.getparent();

system.out.println(loaderparentparent);

}

}

结果为:

sun.misc.launcher$appclassloader@131f71a

sun.misc.launcher$extclassloader@15601ea

null

从上面的例子我们能够很清除的看出classloader的层次关系。

【说明】bootstrap loader为null,是因为它是由c++写的没法表示

此外,大家需要注意的是appclassloader和extclassloader都是urlclassloader的子类别。

appclassloader搜索的路径是由java.class.path取出的路径决定的。

java.class.path值的设定是由-cp 或-classpath或path环境变量决定的。

system.out.println(system.getproperty("java.class.path"));

结果为:

d:\eclipse\workspace3.2.2\classloadertest\bin

extclassloader搜索的路径是由java.ext.dirs决定的。

system.out.println(system.getproperty("java.ext.dirs"));

结果为:

d:\java\jdk1.5\jre\lib\ext

java.ext.dirs的内容是由java.exe选择的jre路径下面的 jre\lib\ext决定的。

最后一个是bootstrap loader了,它是由“sun.boot.class.path”决定的。

system.out.println(system.getproperty("sun.boot.class.path"));

结果为:

d:\java\jdk1.5\jre\lib\rt.jar;d:\java\jdk1.5\jre\lib\i18n.jar;d:\java\jdk1.5\jre\lib\sunrsasign.jar;d:\java\jdk1.5\jre\lib\jsse.jar;d:\java\jdk1.5\jre\lib\jce.jar;d:\java\jdk1.5\jre\lib\charsets.jar;d:\java\jdk1.5\jre\classes

系统参数sun.boot.class.path需要在虚拟机启动前修改。作为java.exe的参数-dsun.boot.class.path = xxxxx

从名称可以看出 sun.boot.class.pathsun.class.path 都是看到的路径,对该路径下面文件夹的东西就不管了。而sun.ext.dirs则告诉我们它会搜索dirs,也就是说如果指定目录没有搜索到就会搜索下层路径。

我们可以看出,使用dirs肯定是一个比较费时的操作,需要去逐层的搜索。

【说明】当jvm启动以后我们的这些参数是不能再修改的了,即使你使用system.setproperty方法也不行。因为在系统启动的时候读取以后就保存了一份。

前面说到了类加载器的结构,有这样一种结构有什么作用呢?下面来说明:

【类加载器可以看到parent加载器的所载入的所有类型,反之不行】

举例:加载class的时候先是appclassloader被调用,appclassloader先请求它的parent加载器extclassloader,extclassloader也先请求它的parent加载器bootstrapclassloader。如果bootstrapclassloader能够加载就ok,以后都只能使用bootstrapclassloader来加载,如果没有找到则返回extclassloader来加载,如果没有找到就到appclassloader去搜索。

需要说明的是,如果你的main所在的类是由extclassloader加载的,如果后续加载的类不在extclassloader和bootstrapclassloader所在的路径下则会出现找不到类的异常,即使appclassloader能够搜索到也不行。

比方说:jdbc api的核心类都是使用bootstrapclassloader来加载的,但是jdbc driver是由extclassloader来加载的。其实不只是jdbc其他的java api都是这样的模式,估计是为了安全考虑。

但是这样岂不是会出现上面所说的找不到类的情况吗?有context class loader来解决。

【java.exe怎么找到jre】

1.java.exe自己的路径下是否有jre目录。

2.父目录下是否有jre目录。

3.查找hkey_local_machine\software\javasoft\java runtime environment下面的javahome指向的路径和runtiemlib指向的jvm.dll(jdk6.0下面的注册表为例)

至于用的是哪个java.exe 找一下环境变量吧!

常见错误 不知道是哪个java.exe 调用了那个jre里面的jvm.dll


======================================================
在最后,我邀请大家参加新浪APP,就是新浪免费送大家的一个空间,支持PHP+MySql,免费二级域名,免费域名绑定 这个是我邀请的地址,您通过这个链接注册即为我的好友,并获赠云豆500个,价值5元哦!短网址是http://t.cn/SXOiLh我创建的小站每天访客已经达到2000+了,每天挂广告赚50+元哦,呵呵,饭钱不愁了,\(^o^)/