从JVM到RTTI

[size=medium]这一切还要从CLASS文件说起。当我们在虚拟机上编写java文件时,虚拟机会动态地将java文件转换为以.class为后缀名的CLASS文件。那么这个ClASS文件又有什么作用呢?为什么我们删除java文件保留ClASS文件,在命令行中执行java filename程序仍能正常运行呢?[/size]

[size=medium][color=red]一、CLASS文件[/color][/size]
[size=medium][color=blue]1、概述[/color][/size]
[size=medium]Java编译器将Java源文件指令翻译成虚拟机能识别的字节码,这些机器码被储存在以.class为扩展名的CLASS文件中。CLASS文件是可以运行在任何支持Java虚拟机的硬件平台和操作系统上的二进制文件。每个CLASS文件都包含一个类或接口的定义和实现。CLASS文件是一个虚拟机的指令集,所有的这些指令都会被程序翻译成目标机器的机器语言。[/size]
[size=medium][color=blue]2、作用[/color][/size]
[size=medium]平台无关性:为Java程序提供独立于底层主机平台的二进制形式的服务。[/size]
[size=medium]网络移动性: CLASS文件设计得紧凑,因此它们可以快速地在网络上传送。其次,由于Java程序是动态连接和动态扩展的,CLASS文件可以在需要的时候才下载。这个特点使得Java应用程序能够安排从网络上下载CLASS文件的时间,从而可以最大限度地减少终端用户的等待时间。[/size]
[size=medium][color=blue]3、链接[/color][/size]
[size=medium]所有的类都是在对其第一次使用,通过类加载器动态地加载到虚拟机中的。然而,类加载器加载的不是Java源文件,而是编译后的CLASS文件,从而可以看出——[color=red]Java文件只是用户和虚拟机的交互,真正对虚拟机有意义的是CLASS文件,它参与了虚拟机与底层的交互[/color]。[/size]

[size=medium][color=red]二、类加载器[/color][/size]
[size=medium][color=blue]1、概述[/color][/size]
[size=medium]类加载机制不只是使用一个单一的类加载器,每个Java程序至少有三个类加载器,它们都是系统类加载器。用户也可以通过实现java.lang.ClassLoader实现自定义类加载器。[/size]
[size=medium][color=blue]2、JVM自带类加载器[/color][/size]
[size=medium]根类加载器(Bootstrap):使用c++编写,该加载器没有父加载器。它负责加载虚拟机的核心类库(典型地,来自rt.jar包下的类库)。根类加载器依赖于底层操作系统,属于虚拟机实现的一部分,它并没有实现java.lang.ClassLoader类。[/size]
[size=medium]扩展类加载器(Extension):纯java代码编写,ClassLoader的子类,它的父加载器为根类加载器。它从java.ext.dirs系统属性所指定的目录中加载类库,或从JDK的安装目录的jre\lib\ext子目录下加载类库。你可以将你要引进的jar文件放在该目录下,加载器不需要任何class path就可以找到它们的类库。[/size]
[size=medium]系统类加载器或应用加载器(System):纯java代码编写,ClassLoader的子类,它的父加载器为扩展类加载器,它从环境变量classpath或系统属性java.class.path所指定的目录中加载类,[color=red]它是用户自定义的类加载器的默认父加载器[/color]。[/size]
[size=medium][color=blue]3、类的加载过程[/color][/size]
[size=medium]类的加载采用父类委托机制,这种机制能够更好的保障Java平台的安全性。在此委托机制中,除Java自带的根加载器外,其余的加载器都有且只有一个父加载器,如下层次图所示,当Java程序请求(your custom)loader加载(your custom)class时,loader首先委托自己的父加载器去加载class,若父加载器能完成,则由父加载器加载,否则由loader加载。([color=red]这种关系并非是继承关系,而是一种包装机制[/color])。[/size]
[img]http://dl.iteye.com/upload/attachment/552784/cc6382f4-d633-3e70-a3f7-32183fd53990.png[/img]
[size=medium][color=blue]4、命名空间[/color][/size]
[size=medium]在Java中一个类通过认证的类全名来唯一的标识。认证的类全名包括包名和类名两部分组成。但是一个被加载到JVM中的类则通过类的全名和加载这个类的类记载器来唯一的标识。每个类加载器都有自己的命名空间,命名空间由该加载器和所有的父加载器所加载的类组成。在同一个命名空间不会出现类的完整名字(包括包名)相同的两个类;在不同的命名空间有可能出现完整的名字包括包名相同的两个类。[/size]
[size=medium][color=blue]5、运行时包[/color][/size]
[size=medium]由同一个类加载器加载的属于相同包的类组成了运行时包,决定两个类是不是属于同一个运行时包,不仅要看它们的包名是否相同,还要看定义类加载器是否相同,[color=red]只有属于同一个运行时包的类才能互相访问包可见的类和类成员[/color],这样的限制能避免用户自定义的类冒充核心类库的类,去访问包可见成员。[/size]
[size=medium][color=blue]6、自定义类加载器[/color][/size]
[size=medium]如上所述,系统类加载器是自定义类加载器的父加载器。若想实现自定义类加载器,需实现java.lang.ClassLoader类。如下示例。[/size]
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;

public class MyClassLoader extends ClassLoader{
private String name;//类加载器的名字
private String path = "F:\\";//加载类的路径
private static final String fileType = ".class";//文件的扩展名
public MyClassLoader(String name){
super();//显式地调用父类构造器,将该加载器的父加载器设置为默认的系统加载器
this.name = name;
}
public MyClassLoader(ClassLoader parent,String name){
super(parent);//设置该加载器的父加载器
this.name = name;
}
//将java文件中的数据存储到字节数组中
private byte[] loadClassData(String name){
byte[] data = null;//存储字节码
InputStream ips = null;
ByteArrayOutputStream baos = null;
try{
String cname = name.replace(".", "\\");//获取正确的路径格式
ips = new FileInputStream(new File(path+cname+fileType));
baos = new ByteArrayOutputStream();
int ch = 0;
while(-1 != (ch = ips.read()) ){
baos.write(ch);//从文件输入流中读取数据,将读到的数据写到字节数组输出流中
}
data = baos.toByteArray();
ips.close();
baos.close();//关闭输入输出流
}catch(Exception ep){
ep.printStackTrace();
}
return data;
}
//获取Class对象
public Class<?> findClass(String name) throws ClassNotFoundException {
byte[] data = this.loadClassData(name);
return this.defineClass(name, data, 0, data.length);
}
public String getPath() {
return path;
}
public void setPath(String path) {
this.path = path;
}
public String toString(){
return this.name;
}
//测试方法
public static void test(ClassLoader loader) throws Exception{
Class<?> c = loader.loadClass("First");
c.newInstance();
}
public static void main(String[] args) throws Exception {
MyClassLoader loader1 = new MyClassLoader("loader1");
loader1.setPath("F:\\myapp\\loader1\\");
MyClassLoader loader2 = new MyClassLoader(loader1,"loader2");
loader2.setPath("F:\\myapp\\loader2\\");
test(loader1);
test(loader2);
}
}

public class First {
public First(){
System.out.println("First的类加载器是:"+this.getClass().getClassLoader());
new Second();
}
}

public class Second {
public Second(){
System.out.println("Second的类加载器是:"+this.getClass().getClassLoader());
}
}

[size=medium]只需要将要加载的类放到类加载器的路径下,即可在命令行完成测试。当然,只需将程序略改一二,就能完成对父委托机制的测试,此处不再讨论。[/size]
[size=medium][color=blue]7、链接[/color][/size]
[size=medium]随着Class对象的产生,我们已经越来越接近RTTI(运行时类型信息)了。之所以这么说,是因为Class对象构成了RTTI的核心,它能使程序在运行时确定类的确切类型,而这正是强大的多态机制避及的问题。一旦我们获得类的确切类型,就能进行一些特定的操作,而不是基于基类通用的操作,丰富了程序的行为。不仅如此,Class对象还让我们在运行时打开和检查CLASS文件成为可能,从而实现了反射机制。还有诸如注册工厂、动态代理等设计模式的实现。[color=red]这一切都在酝酿之中~~[/color]...[/size]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值