黑马程序员_JAVA类加载机制

------- android培训java培训、期待与您交流! ----------

(一) 预先加载与按需加载

        Java运行环境为了提高程序的执行速度,在JRE运行的开始会预先将运行所需要的基础类加载到内存中,这些必须的类是java程序运行过程中的基石,包括JRErt.jar文件里面的所有的class文件。

        当 java.exe虚拟机开始运行以后,它会找到安装在机器上的 JRE环境,然后把控制权交给JREJRE的类加载器会将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培训、期待与您交流! ----------
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值