既然谈到类的初始化,那就不得不把VM(Virtual Machine,虚拟机)先做个简单的介绍...
VM,是Java程序运行的核心。它是不可视的,也就是说你在你的机器上根本找不到它。那么它到底是怎么产生的呢?其实它只是一个dll格式文件(全称是jvm.dll,该文件隐藏在jre目录下)。初始化VM的过程:java命令解释*.class文件时 ----> 通过环境变量找到JRE目录并产生JRE ----> 在JRE目录下,寻找jvm.dll文件,并初始化一个jvm (VM的更详细介绍请自行下载观看JVM工作原理)
当我们需要运行一个类时,过程且看图片“类的生命周期”。 本文只着重介绍到“初始化”一步,并尽可能的用代码证明得出的结论。 重申:类的连接分为验证、准备、解析
1、装载
也称类的加载。它是指将类的class文件(本地磁盘或者网络上)加载至VM内存中,并为之创建一个对应的java.lang.Class对象。(这部分内容本人正在消化中,代码消化完贴出...)2011.5.29 补上
加载类的加载器有三种:
1、Bootstrap ClassLoader:根类加载器,也称引导类加载器。它是VM自动实现的,代码是用c语言实现的,所以你在IDE中是找不到它的源代码滴。 它负责加载jdk中的系统类,加载Java的核心类如String。
2、Extension ClassLoader:扩展类加载器。它负责加载JRE的扩展目录(JAVA_HOME/jre/lib/ext或者由java.ext.dirs系统指定的目录)中JAR的类包。
3、System ClassLoader:系统类加载器。它负责在JVM启动时,加载来自命令java -classpath或者java.class.path系统属性,或CLASSPATH环境变量所指定的JAR包和类路径。一般都认为系统类加载器是加载应用程序第一个类的加载器
代码如下
package com.ClassTests;
/**************************************************
*
* @author: 瘋叻ハ.兩
* @create-time: 2011-5-22 下午07:13:14
* @revision: 1.0
* @purpose: 类的加载
*
**************************************************/
public class LoadClass {
public static void main(String[] args){
// 查看根加载器加载的类的路径
// URL[] urls = sun.misc.Launcher.getBootstrapClassPath().getURLs();
// for(int i = 0; i < urls.length; i++){
// System.out.println(urls[i].toExternalForm());
// }
// 获取JVM的名称
System.out.println("JVM名称是:"+System.getProperty("java.vm.name"));
// System.out.println("Java 类路径:"+System.getProperty("java.class.path"));
// System.out.println("加载库时搜索的路径列表:"+System.getProperty("java.library.path"));
// System.out.println("用户的主目录:"+System.getProperty("user.home"));
// System.out.println("用户的当前工作目录:"+System.getProperty("user.dir"));
// 获取系统类的加载器
ClassLoader cl = ClassLoader.getSystemClassLoader();
System.out.println("系统的类加载器是:"+cl);
// 获取加载器的类名
System.out.println("类加载器的类名是:"+cl.getClass());
// 获取扩展类加载器,因为是系统类加载器的父类
ClassLoader clp = cl.getParent();
System.out.println("系统的类加载器的父类加载器是:"+clp);
// 获取扩展类加载器的类名
System.out.println("系统的类加载器的父类加载器的类名是:"+clp.getClass());
// 获取根类加载器(结果是null,所以如果进一步获取类名,报NullPoint异常)
System.out.println("根类加载器是:"+clp.getParent());
}
}
运行结果:JVM名称是:Java HotSpot(TM) Client VM
系统的类加载器是:sun.misc.Launcher$AppClassLoader@19821f
类加载器的类名是:class sun.misc.Launcher$AppClassLoader
系统的类加载器的父类加载器是:sun.misc.Launcher$ExtClassLoader@addbf1
系统的类加载器的父类加载器的类名是:class sun.misc.Launcher$ExtClassLoader
根类加载器是:null
分 析: Java程序执行的流程:*.class --> 通过环境变量找到JRE目录 --> 在该目录找到jvm.dll --> JVM初始化 --> 产生BootstrapLoader --> 载入ExtClassLoader --> 载入AppClassLoader --> 装载*.class --> 字节码校验 --> 解析器执行
2、验证
验证阶段是用于检验被加载的类是否有正确的内部结构,并和其他类协调一致(毫无代码可言)
3、准备
准备阶段是负责给类的静态属性分配内存,并设置默认初始值,而不是初始化(注意区别)。
代码如下
package com.ClassTests;
/**************************************************
*
* @author: 瘋叻ハ.兩
* @create-time: 2011-5-21 下午08:21:25
* @revision: 1.0
* @purpose: 探秘类的准备阶段发生的事情
*
***************************************************/
public class ClassReady {
static{
b = 4;
}
static int b;
public static void main(String[] args) {
System.out.println(b);
}
}
运行结果:4
分 析 :首先第一个问题,为什么在静态初始块中b没有定义类型不报错?其次,结果为什么是4,而不是int的默认值0? 其实正是在类的准备阶段,VM为类的静态属性分配内存,使得内存中已然存在了b变量,所以在类的初始化阶段,虽未给b定义类型,但系统也不报错。接下来进行类的初始化,只有静态代码块的初始化,所以,因此结果为4,证明了类在准备阶段只为类的静态属性分配内存,而并没有执行它的成员动作。
4、解析
解析阶段是将类的二进制数据中的符号引用替换成直接引用。(也无代码可言)
5、类的初始化
类的初始化阶段,VM负责对类进行初始化,主要是对类属性(静态属性)进行初始化,方式有2:(1)声明静态属性时指定初始值;(2)使用静态初始化块为静态属性指定初始值。
代码如下
package com.ClassTests;
/**************************************************
*
* @author: 瘋叻ハ.兩
* @create-time: 2011-5-21 下午08:40:58
* @revision: 1.0
* @purpose: 类的初始化方式
*
***************************************************/
public class ClassInition {
static int a = 5; // 声明时指定初始值
static int b;
static int c = 7;
static{
b = 6; // 声明时未指定,可以在静态初始块中补上
c = 8; // 如果两种都有,结果取后一个
}
public static void main(String[] args) {
System.out.println("a的初始化值是 "+a);
System.out.println("b的初始化值是 "+b);
System.out.println("c的初始化值是 "+c);
}
}
运行结果:a的初始化值是 5
b的初始化值是 6
c的初始化值是 8
分 析 :一切都按部就班初始化着
5、实例初始化
在进行玩类的初始化,就会进行实例初始化。过程与类的初始化不相上下。
代码如下:
package com.ClassTests;
/**************************************************
*
* @author: 瘋叻ハ.兩
* @create-time: 2011-5-21 下午01:27:04
* @revision: 1.0
* @purpose: 简析类的生命周期及类的初始化
*
***************************************************/
/**************************************************
*
* @author: 瘋叻ハ.兩
* @create-time: 2011-5-21 下午02:25:46
* @revision: 1.0
* @purpose: 类的实例化
*
***************************************************/
class Parents{
public Parents(){
System.out.println("---此时调用了子类的构造方法,父类的初始化开始...");
}
}
public class ClassLife extends Parents {
private static int c;
public ClassLife(){
//super(); //有无super()结果一致,说明程序默认优先初始化直接父类,然后再子类。如果需要显示调用,super()必须放在第一句
System.out.println("---此时调用了子类构造方法,子类的初始化开始...");
}
// 静态初始化块
static {
System.out.println("---执行了类的初始化...");
System.out.println(c);
}
// 初始化块
{
System.out.println("---执行了对象的初始化块...");
// 问题1: 为什么没有声明b,而可以使用4。
b = 4;
}
int b;
public static void main(String[] args) {
ClassLife cl = new ClassLife();
System.out.println(cl.b); // 对象属性的初始化与类的属性初始化一致
}
}
运行结果:---执行了类的初始化...
---此时调用了子类的构造方法,父类的初始化开始...
---执行了对象的初始化块...
---此时调用了子类构造方法,子类的初始化开始...
4
分 析 : 认真看上面的类的初始化
最后归纳下对象创建过程发生的事:
给类的属性在分配内存 ----> 初始化类的属性 ----> 给对象属性分配内存 ----> 在初始块中初始化对象属性 ---->如果有父类,优先构造初始化父类 ----> 调用子类的构造器,创建对象。