从事java研发必然少不了对java类加载机制的涉及,本文结合例子讲述java classloader工作机制。
一 jvm 类加载机制
1)jvm位置:java是运行在java虚拟机上的程式,java虚拟机物理层面上来讲,就是我们安装在电脑上的jre目录/lib/jvm.dll(版本不同,可能存在于jre目录/lib/client/jvm.dll,jre目录/lib/server/jvm.dll),这是java字节码运行的基础,它不是由java语言编写,所以我们阅读jdk源码时遇到native函数,基本上就是调用jvm相关的代码。
2)jdk和jre关系:从oracle官网上下载java环境,可以选择jdk或者jre进行安装,他们的关系可以理解为子集的概念,jdk是jre运行环境再加上一些java开发的工具集,查看jdk目录结构如下(例子为jdk1.6.37版本)
D:.
java官方文档描述jre和jdk关系如图:(链接http://docs.oracle.com/javase/7/docs/)
在安装jdk时可以选择是否同时安装jre,如果选择安装,那么系统中就存在两份jre,具体程序运行时会执行哪个jre,windows系统默认搜索规则是:
1. 当前目录下有沒有 JRE子目录
2. 父目录下 JRE 子目录
3.查 詢 Window Registry(HKEY_LOCAL_MACHINE\Software\JavaSoft\Java Runtime Environment\)
注意:安装环境会建议建立JAVA_HOME环境变量并将其加入path中,这样可以避免因为默认搜索规则出的结果造成混淆。如果没有加,可以搜索下自己系统中有几个java.exe,本人系统中有三个,分别在c:/windows/system32/java.ext;
d:/program files/java/jdk_1_6_37/bin/java.exe;
d:/program files/java/jre/bin/java.exe
因为没有将JAVA_HOME路径加入到path,path路径是c:/windows/system32;......所以在命令行下执行java Main系统默认执行的是c:/windows/system32/java.exe(除非命令行在其他两个java.exe所在目录),这一点可以通过分别修改三个路径下java.exe文件到新名字java1.exe来验证到底执行的是哪个目录
3)java类加载机制:jdk带有三个系统类加载器:bootstrap加载器;扩展加载器;系统加载器,他们的关系如下表
类加载器
被加载加载器
parent
父类
类型
默认加载目录/文件
备注
bootstrap加载器
sun.boot.class.path系统属性所指路径,指向jre下/lib,如rt.jar
虚拟机出于安全等因素考虑,不会加载< Java_Runtime_Home >/lib存在的陌生类,开发者通过将要加载的非JDK自身的类放置到此目录下期待启动类加载器加载是不可能的
扩展加载器
bootstrap加载器
bootstrap加载器(因为此加载器由非java语言编写,在jvm中标识为null,所以一个加载器的parent为null表示它是由bootstrap加载器加载)
java.lang.ClassLoader->java.security.SecurClassLoader->java.net.URLClassLoader
sun.misc.Launcher$ExtClassLoader
java.ext.dirs属性所指路径,指向java.exe所在jre下/lib/ext子目录,可以将自己的class文件放入这个目录,交由扩展加载器加载,可以通过–Djava.ext.dirs=xxx 改变
jvm中只存在一份,一旦建立,再通过System.setProperty()修改系统属性不会起作用
系统加载器
bootstrap加载器
扩展加载器
java.lang.ClassLoader->java.security.SecurClassLoader->java.net.URLClassLoader
sun.misc.Launcher$AppClassLoader
默认为.目录
再取java.class.path属性所指路径,可以通过java -cp xxx 来改变
最后取环境变量CLASSPATH下的class文件和jar文件
jvm中只存在一份,一旦建立,再通过System.setProperty()修改系统属性不会起作用
在 Java 之中,每个类都是由某个类型加载器(ClassLoader 的实体)来载入,因此,Class 类型的实体中,都会有记录载入它的ClassLoader 的实体(注意:如果值是null,不代表它不是由类加载器载入,而是代表这个类別是由(bootstrap loader,也有人称root loader)所载入,只不过这个类型加载器不由java书写,所以逻辑上没有实体 )
二 自定义类加载器
加载类到内存中分两种方式:1)预加载 ;2)显示加载。预加载是虚拟机在启动的时候将rt.jar中的类一次加载到内存,因为这些类都是基础类,会被频繁使用到,预加载可以减少运行时IO开销,显示加载可以:1)使用new()操作符 2)java.lang.Class 裡的forName() 3)java.lang.ClassLoader 裡的loadClass()
要查看类加载详情,可以使用java -verbose:class xxx来输出。看下面一段代码:
到Main所在目录执行javac *.java,查看生成了三个calss文件,再执行java -verbose:class Main > load.log ,查看load.log内容如下:
由此可见class类加载顺序。
也可以使用以下方式加载类:
了解了默认类加载机制后,可以手工打造一个加载器,ExtClassLoader和AppClassLoader都是继承URLClassLoader,自己的加载器也可以继承自这个类:
deep_java/test/ 目录结构如下:
Assembly 是一个接口,ClassA ClassB ClassC都实现了这个接口,Test主程序在运行时将参数名作为Class名动态加载。命令行输入java -verbose:class Test ClassA 执行结果如下: