虚拟机类加载机制

类的生命周期

类从被加载到虚拟机内存中开始,到卸载出内存为止,它的整个生命周期包括:加载 Loading)、验证(Verification)、准备(Preparation)、解析(Resolution)、初始化 (Initialization)、使用(Using)和卸载(Unloading)7个阶段。其中验证、准备、解析3个 部分统称为连接(Linking),这7个阶段如下所示:

 

类加载的过程

1、加载

“加载”是“类加载”(Class Loading)过程的一个阶段,在加载阶段,虚拟机需要完成以下3件事情:

1)通过一个类的全限定名来获取定义此类的二进制字节流。(从Class文件或网络或Zip包等获取这个二进制字节流) 

2)将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构。

3)在内存中生成一个代表这个类的java.lang.Class对象,作为方法区这个类的各种数据 的访问入口。

2、验证

验证是连接阶段的第一步,这一阶段的目的是为了确保Class文件的字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机自身的安全。

(1)文件格式验证第一阶段要验证字节流是否符合Class文件格式的规范,并且能被当前版本的虚拟机处理。

(2)元数据验证第二阶段是对字节码描述的信息进行语义分析,以保证其描述的信息符合Java语言规范的要求(如是否继承了不被允许继承的类)

(3)字节码验证:主要目的是通过数据流和控制流分析,确定程序语义是合法的、符合逻辑的。

(4)符号引用验证:验证符号引用是否合法(如是否可以通过字符串描述的全限定名是否能找到对应的类)。这一个验证阶段发生在类的解析阶段将符号引用转化直接引用的时候。

3、准备

准备阶段是正式为类变量分配内存并设置类变量初始值的阶段,这些变量所使用的内存都将在方法区中进行分配。如public static int value=123;在准备阶段会为static变量value分配内存并初始化零值。如果这个value再用final修饰,那么在准备阶段value就等于123。

4、解析

解析阶段是虚拟机将常量池内的符号引用替换为直接引用的过程解析动作主要针对类或接口、字段、类方法、接口方法、方法类型、方法句柄和调用点限定符7类符号引用进行。

符号引用与直接引用的区别?

符号引用(Symbolic References):符号引用以一组符号来描述所引用的目标,符号可 以是任何形式的字面量,只要使用时能无歧义地定位到目标即可。符号引用与虚拟机实现的 内存布局无关,引用的目标并不一定已经加载到内存中。各种虚拟机实现的内存布局可以各 不相同,但是它们能接受的符号引用必须都是一致的,因为符号引用的字面量形式明确定义 在Java虚拟机规范的Class文件格式中。

直接引用Direct References):直接引用可以是直接指向目标的指针、相对偏移量或是 一个能间接定位到目标的句柄。直接引用是和虚拟机实现的内存布局相关的,同一个符号引 用在不同虚拟机实例上翻译出来的直接引用一般不会相同。如果有了直接引用,那引用的目 标必定已经在内存中存在。

5、初始化

类初始化阶段是类加载过程的最后一步,前面的类加载过程中,除了在加载阶段用户应用程序可以通过自定义类加载器参与之外,其余动作完全由虚拟机主导和控制。到了初始化阶段,才真正开始执行类中定义的Java程序代码在初始化阶段,则根据程序员通 过程序制定的主观计划去初始化类变量和其他资源

类和类的加载器

类加载器虽然只用于实现类的加载动作,但它在Java程序中起到的作用却远远不限于类加载阶段。对于任意一个类,都需要由加载它的类加载器和这个类本身一同确立其在Java虚 拟机中的唯一性,每一个类加载器,都拥有一个独立的类名称空间。

看下面一段代码: 

/**
*类加载器与instanceof关键字演示
*
*@author zzm
*/
public class ClassLoaderTest{
public static void main(String[]args)throws Exception{
ClassLoader myLoader=new ClassLoader(){
@Override
public Class<?>loadClass(String name)throws ClassNotFoundException{
try{
String fileName=name.substring(name.lastIndexOf(".")+1)+".class";
InputStream is=getClass().getResourceAsStream(fileName);
if(is==null){
return super.loadClass(name);
}
byte[]b=new byte[is.available()];
is.read(b);
return defineClass(name,b,0,b.length);
}catch(IOException e){
throw new ClassNotFoundException(name);
}
}
};
Object obj=myLoader.loadClass("org.fenixsoft.classloading.ClassLoaderTest").newInstance();
System.out.println(obj.getClass());
System.out.println(obj instanceof org.fenixsoft.classloading.ClassLoaderTest);
}
}

运行结果:

class org.fenixsoft.classloading.ClassLoaderTest

false

第二行结果出现false是为虚拟机中存在了两个ClassLoaderTest类,一个 是由系统应用程序类加载器加载的,另外一个是由我们自定义的类加载器加载的,虽然都来 自同一个Class文件,但依然是两个独立的类,做对象所属类型检查时结果自然为false。

双亲委派模型

Java虚拟机的角度来讲,只存在两种不同的类加载器:一种是启动类加载器 Bootstrap ClassLoader),这个类加载器使用C++语言实现[1],是虚拟机自身的一部分;另 一种就是所有其他的类加载器,这些类加载器都由Java语言实现,独立于虚拟机外部,并且 全都继承自抽象类java.lang.ClassLoader。

Java开发人员的角度来看,绝大部分Java程序都会使用到以下3种系统提供的类加载器。

启动类加载器Bootstrap ClassLoader):前面已经介绍过,这个类将负责将存放在 JAVA_HOME>\lib目录中的,或者被-Xbootclasspath参数所指定的路径中的类的加载。

扩展类加载器Extension ClassLoader):这个加载器由sun.misc.Launcher $ExtClassLoader实现,它负责加载JAVA_HOME>\lib\ext目录中的,或者被java.ext.dirs系统变量所指定的路径中的所有类库,开发者可以直接使用扩展类加载器。

应用程序类加载器Application ClassLoader):这个类加载器由sun.misc.Launcher $AppClassLoader实现。由于这个类加载器是ClassLoader中的getSystemClassLoader()方法的返回值,所以一般也称它为系统类加载器。它负责加载用户类路径(ClassPath)上所指定的类 库,开发者可以直接使用这个类加载器,这是程序中默认的类加载器

现在我们再来讨论双亲委派模型,看下图:

  

图中展示的类加载器之间的这种层次关系,称为类加载器的双亲委派模型(Parents Delegation Model)。双亲委派模型要求除了顶层的启动类加载器外,其余的类加载器都应当有自己的父类加载器。这里类加载器之间的父子关系一般不会以继承(Inheritance)的关系来实现,而是都使用组合Composition)关系来复用父加载器的代码。

双亲委派模型的工作过程:如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器去完成,每一个层次的类加载器都是如此,因此所有的加载请求最终都应该传送到顶层的启动类加载器中,只有当父加载器反馈自己无法完成这个加载请求(它的搜索范围中没有找到所需的类)时,子加载器才会尝试自己去加载。

思考

可否自己定义一个java.lang.String类呢?

可以定义,语法上不会出错,但是用不了。当我自己去定义一个java.lang.String类,一运行发现双亲委派模型是这么个意思:

 

运行结果:

 

为什么说找不到main方法呢?我们明明定义了一个main方法呀!原因就在于双亲委派模型,虽然我们自定义了String类,在我们运行String类的时候需要先去加载String类,最终这个String类实际上是由bootstrap类加载器去加载的,而java里面的String类是本来就没有main方法的。所以出现了找不到main方法。

现在再来看双亲委派模型的好处,这样就更好理解了。

双亲委派模型有什么好处?或者说为什么这么做?

可以避免重复加载一个类,优先由父类加载器去加载一个类,就没有必要让子加载器再去加载一次。还有就是安全因素,避免用户自定义的重名类代替Java API中已经实现的类,这样会有安全隐患。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值