(一) 预先加载与按需加载
Java运行环境为了提高程序的执行速度,在JRE运行的开始会预先将运行所需要的基础类加载到内存中,这些必须的类是java程序运行过程中的基石,包括JRE的rt.jar文件里面的所有的class文件。
当 java.exe虚拟机开始运行以后,它会找到安装在机器上的 JRE环境,然后把控制权交给JRE,JRE的类加载器会将lib目录下的rt.jar基础类别文件库加载进内存,这些文件是 Java程序执行所必须的,所以系统在开始就将这些文件加载,避免以后的多次IO操作,从而提高程序执行效率。
相对于预先加载,我们在程序中需要使用自己定义类的时候就要使用依需求加载方法( load-on-demand),就是在 Java 程序需要用到的时候再加载,以减少内存的消耗,因为 Java 语言的设计初衷就是面向嵌入式领域的。
我们在程序中定义一个类的实例时,比如 LampController lc , 这个时候lc的值为null,也就是说lc还没有初始化,没有调用LampController 的构造函数,只有当执行lc = new LampController() 以后,JRE才真正的将LampController 加载进来。
public class Main {
public static void main(String[] args) {
LampController lc ;
String [] directions = new String[]{
"S2N","S2W","E2W","E2S",
"N2S","N2E","W2E","W2N",
"S2E","E2N","N2W" ,"W2S"
};
for (int i=0;i<directions.length;i++){
new Road(directions[i]);
}
lc = new LampController();
}
}
编译之后执行命令:java -verbose:class su.wen.jun.traffic.Main > ret.txt ,从运行结果我们可以看到,JRE首先加载 Main文件,然后再加载 LampController文件,从而实现了按需加载。
[Loaded su.wen.jun.traffic.LampController from file:/C:/Develop/Project/Java%20Project/Traffic/classes/]
[Loaded su.wen.jun.traffic.Lamp from file:/C:/Develop/Project/Java%20Project/Traffic/classes/]
[Loaded java.util.concurrent.locks.ReadWriteLock from shared objects file]
[Loaded java.util.concurrent.locks.ReentrantReadWriteLock from shared objects file]
[Loaded java.util.concurrent.locks.ReentrantReadWriteLock$Sync from shared objects file]
[Loaded java.util.concurrent.locks.ReentrantReadWriteLock$FairSync from C:\Program Files\Java\jre6\lib\rt.jar]
[Loaded java.util.concurrent.locks.ReentrantReadWriteLock$Sync$ThreadLocalHoldCounter from shared objects file]
[Loaded java.util.concurrent.locks.ReentrantReadWriteLock$ReadLock from shared objects file]
[Loaded java.util.concurrent.locks.ReentrantReadWriteLock$WriteLock from shared objects file]
[Loaded java.lang.Class$4 from shared objects file]
[Loaded sun.reflect.NativeMethodAccessorImpl from shared objects file]
[Loaded sun.reflect.DelegatingMethodAccessorImpl from shared objects file]
[Loaded su.wen.jun.traffic.LampController$1 from file:/C:/Develop/Project/Java%20Project/Traffic/classes/]
S2N_1 is travaling
(二) 隐式加载和显性加载
隐式加载:程序在运行过程中当碰到通过new方式产生对象的时候,隐式的调用类装载器加载对应的类到jvm .也就是JRE在执行到 new关键字的时候就会把对应的实例类加载进入内存
显示加载:程序员自己写程序把需要的类加载到内存当中,例如下面的实例
public class Main {
public static void main(String[] args) {
Class c = Class.forName("DemoClass1");
DemoClass1 instance= (DemoClass1)c.newInstance();
instance.method();
}
}
,上面的实例中,通过Class类的forName(String clsName) 方法把自定义的类DemoClass1加载进来,并通过newInstance()方法把实例产生出来,这种方式一般应用于框架中。forName方法还有一种重载的形式:
Class forName(String s, boolean flag, ClassLoader classloader)
如果要使用这个方法,我们就要指定一个类加载器,
Class forName("DemoClass1", true,Main.class.getClass().getClassLoader() );
第二个参数默认为true,如果为true,则加载类的过程中会执行静态代码块,而forName(String s) 本身就是加载静态代码块的。
说明:两个Class.forName都只是加载类的字节码到内存中,没有产生实例,只有当调用newInstance的时候才会初始化类,调用构造方法。
(三) 父类委托机制
当我们在命令行输入 java Main 的时候,java.exe根据一定的规则找到JRE。接着找到JRE之中的JVM.DLL(真正的java虚拟机),最后载入这个动态链接库启动java虚拟机。虚拟机启动后先会做一些初始化的动作,比如读取系统参数,初始化完成之后就会产生第一个类加载器BootstrapLoader,Bootstrap Loader是由C++编写而成,Bootstrap Loader本身也会做一些必要的初始工作,其中最重要的就是加载Launcher.java中的ExtClassLoader,并设其Parent为 null,并不是代表其没有父类加载器,而是因为Bootstrap Loader由C++实现的原因,在委托机制中其委托的父类就是Bootstrap Loader,因此其父加载器为Bootstrap Loader .
接着Bootstrap Loader会加载Launcher.java中的AppClassLoader,并设定其父加载器为之前的ExtClassLoader. Launcher$ExtClassLoader.class 与 Launcher$AppClassLoader.class都是由Bootstrap Loader所加载,所以Parent和由哪个类加载器并没有直接的关系!
类加载器在Java中是以java.lang.ClassLoader类型存在:
java.lang.Object
java.lang.ClassLoader
每一个类被加载后,都会有一个Class的实例来代表,而每个Class的实例都会有一个属性记录自己是由哪个ClassLoader加载的。因此由Class的getClassLoader()可以获得加载该类的ClassLoader,而从ClassLoader的getParent()方法可以取得自己的parent :
public class ParentLoader {
public static void main(String[] args) {
ClassLoader loader = ParentLoader.class.getClassLoader();
System.out.println("loader = "+loader+" , loader parent = "+loader.getParent());
loader = loader.getParent();
System.out.println("loader = "+loader+" , loader parent = "+loader.getParent());
loader = loader.getParent();
System.out.println("loader = "+loader+" , loader parent = "+loader.getParent());
}
}
执行结果:
loader = sun.misc.Launcher$AppClassLoader@19821f , loader parent = sun.misc.Launcher$ExtClassLoader@addbf1
loader = sun.misc.Launcher$ExtClassLoader@addbf1 , loader parent = null
Exception in thread "main" java.lang.NullPointerException
at su.wen.jun.traffic.ParentLoader.main(ParentLoader.java:14)
这几个Loader分别从以下路径寻找程序所需要的类:
BootstrapLoader:sun.boot.class.path
ExtClassLoader:java.ext.dirs
AppClassLoader:java.class.path
public class GetLoadPath {
public static void main(String[] args) {
System.out.println("sun.boot.class.path:");
String str[] = System.getProperty("sun.boot.class.path").split(";");
for(String s : str){
System.out.println(s);
}
System.out.println("java.ext.dirs:");
str = System.getProperty("java.ext.dirs").split(";");
for(String s : str){
System.out.println(s);
}
System.out.println("java.class.path:");
str = System.getProperty("java.class.path").split(";");
for(String s : str){
System.out.println(s);
}
}
}
执行结果:
sun.boot.class.path:
C:\Program Files\Java\jre6\lib\resources.jar
C:\Program Files\Java\jre6\lib\rt.jar
C:\Program Files\Java\jre6\lib\sunrsasign.jar
C:\Program Files\Java\jre6\lib\jsse.jar
C:\Program Files\Java\jre6\lib\jce.jar
C:\Program Files\Java\jre6\lib\charsets.jar
C:\Program Files\Java\jre6\classes
java.ext.dirs:
C:\Program Files\Java\jre6\lib\ext
C:\Windows\Sun\Java\lib\ext
java.class.path:
C:\Develop\Project\Java Project\Traffic\classes
加载Class的时候先是AppClassLoader被调用,AppClassLoader先请求它的parent加载器ExtClassLoader,ExtClassLoader再请求它的parent加载器BootstrapClassLoader。如果BootstrapClassLoader能够加载,这个类就被Bootstrap ClassLoader加载,以后都只能使用BootstrapClassLoader来加载,如果没有找到则返回ExtClassLoader来加载,如果也没有找到,就再返回到AppClassLoader去搜索。所以实际的加载类顺序为:Bootstrap Loader——》Extended Loader——》System Loader的顺序来寻找类,若都无法加载,则会抛出NoClassDefFoundError.
JDK中还有几个类加载器:
java.net.URLClassLoader
java.security.SecureClassLoader
java.rmi.server.RMIClassLoader
sun.applet.AppletClassLoader
(四) 自定义类加载器
其实我们是可以自己定义类加载器的。利用 Java提供的 java.net.URLClassLoader类就可以实现,示例如:
public class DemoClass1 {
static {
System.out.println(" Executing DemoClass1's static block !");
}
public void sayHello(){
System.out.println("Hello World! This is Class DemoClass1 ---------->>");
}
}
@Test
public void test4() throws MalformedURLException, ClassNotFoundException,
InstantiationException, IllegalAccessException {
URL url = new URL("file:\\C:\\Develop\\Project\\Java Project\\Traffic\\classes\\su\\wen\\jun\\traffic");
URLClassLoader urlCls = new URLClassLoader(new URL[] { url });
Class c = urlCls.loadClass("su.wen.jun.traffic.DemoClass1");
System.out.println("before instance!");
DemoClass1 instance = (DemoClass1) c.newInstance();
instance.sayHello();
}
执行结果
before instance!
Executing DemoClass1's static block !
Hello World! This is Class DemoClass1 ---------->>
URL指定类加载器从何处加载类, URL可以指向网际网络上的任何位置,也可以指向我们计算机里的文件系统 (包含 JAR文件 )。上述范例当中我们从file:\\C:\\Develop\\Project\\Java Project\\Traffic\\classes\\su\\wen\\jun\\traffic处寻找类,然后用 URLClassLoader来加载所需的类,最后即可使用该实例了。
如下做一个小对比:
@Test
public void test5() throws ClassNotFoundException, InstantiationException, IllegalAccessException{
Class c = Class.forName("su.wen.jun.traffic.DemoClass1");
System.out.println("before instance!");
DemoClass1 instance = (DemoClass1) c.newInstance();
instance.sayHello();
}
执行结果:
Executing DemoClass1's static block !
before instance!
Hello World! This is Class DemoClass1 ---------->>
从上面的结果可以看出,loadClass()方法加载类的时候,类的静态代码块是不会被执行的。
------- android培训、 java培训、期待与您交流! ----------