java程序的完整执行流程

下午花了一下午的时间,仔细研读了java编程思想和深入理解JVM,总结了一下java程序的执行流程。

1.首先就是运用javac工具对java源程序进行编译,生成对应的与平台无关的字节码文件.class,对于未提供构造器的类,编译器会默认添加一个不带方法体内容的构造器,在class中的名称为<init>。
下面为实例代码

import java.util.Random;
class Initable {
	static final int staticFinal = 47;
	static final int staticFinal2 = ClassInitialization.rand.nextInt(1000);
	static {
		System.out.println("Initializing Initable");
	}
}
class Initable2 {
	static int staticNonFinal = 147;
	static {
		System.out.println("Initializing Initable2");
	}
}
class Initable3 {
	static int staticNonFinal = 74;
	static {
		System.out.println("Initializing Initable3");
	}
}
public class ClassInitialization {
	public static Random rand = new Random(47);
	public static void main(String[] args) throws ClassNotFoundException {
		Class initable = Initable.class;
		System.out.println("After Creating Initable ref");
		System.out.println(Initable.staticFinal);
		System.out.println(Initable.staticFinal2);
		System.out.println(Initable2.staticNonFinal);
		Class initable3 =Class.forName("Initable3");
		System.out.println("After Creating Initable3 ref");
		System.out.println(Initable3.staticNonFinal);
	}
}
2.对包含有main()方法的字节码文件执行java运行操作,开始执行java程序
-->执行ClassInitialization的main()方法,虚拟器发现ClassInitialization的class文件并没有加载到虚拟机内存中。于是开始执行ClassInitialization的类加载,由系统类加载器在classpath下寻找与ClassInitialization类名相同的class文件。由于JDK是传统的双亲委派模型,因而系统类加载器会先把加载该类的请求委派给父类扩展类加载器去执行,扩展类加载器则把请求委派给引导类加载器去执行。然而引导类加载器在自己的搜索范围内未找到这个类,则交给扩展类加载器去加载,同样扩展类加载器也没有找到相应的类,在继续交给自己的子类去加载,终于在系统类加载器中找到了该类的class文件,于是系统类加载器开始尝试加载ClassInitialization.class
-->真正的类加载开始执行:主要分为三个主要的步骤:加载,链接以及初始化。
a.加载:加载跟类加载是两个不同的概念。加载只是类加载的一个步骤。在此步骤中,系统类加载器把ClassInitialization.class文件读入到内存中,并将此文件代表的静态结构存储为jvm内存中方法区的运行时数据结构,同时为该class创建一个代表该类的Class对象,也是存储在方法区中(这与平常的java对象存储在队中有所不同),而该Class对象也将作为程序访问方法区内部数据结构的接口。
b.连接:又细分为三个步骤
-->验证:检查载入Class文件数据的正确性,检查其格式是否符合当前虚拟机的要求
-->准备:给类的静态变量分配存储空间(方法区),并进行默认初始化,该阶段不包括实例变量的初始化(实例变量是随着new的时候随着对象一起分配到堆中)public static Random rand = new Random(47)这句话rand的值为null而不是Random(47)。
-->解析:将class文件中的常量池内的符号引用替换为直接引用的过程。(可以在加载类的过程中进行解析,又或者是等到符号引用被使用前去解析)
c.初始化:除了加载过程可以使用自定义的类加载器之外,其他步骤均是JVM主导与控制的。到了该阶段开始执行字节码文件。该阶段执行的是构造器为该类构造的<clinit>方法,<clinit>方法是编译器收集类中所有的类变量的赋值动作和静态语句块中的语句合并产生的。且与<init>,即类的构造器不同,它会自动的调用父类的<clinit>方法,而不用显式的去调用。此时也开始加载父类的class文件等等,因为使用到了<clinit>中的静态成员变量。
另外,如果类中不存在静态代码块或者类变量的赋值动作的话,则编译器不会生成<clinit>。
输出结果:


After Creating Initable ref
47
Initializing Initable
258
Initializing Initable2
147
Initializing Initable3
After Creating Initable3 ref
74

在准备阶段rand值为null,在初始化之后rand为随机的一个Random对象。而后开始执行main方法,29行Initable.class并不会引发Initable的初始化,只是获得了一个CLass引用,31行打印Initable.staticFinal的值,访问了类Iniable的静态变量,但由于Initable.staticFinal变量是static final类型的,这个变量(编译器常量)在编译阶段通过“常量传播优化”就已经存储在了ClassInitialization的常量池中了(可以说已经被初始化了),也就是说对该变量的读取并不需要对Initial类进行初始化就可以得到。故只输出了47,而其他的静态域和静态代码块则未被初始化。而接下来的32行,
Initable.staticFinal2则就不是编译期常量了,并且有明显的类变量的赋值行为,触发了Initial的初始化。先输出静态代码块中的值,而后是Initable.staticFinal2的值258。33行同理。34行Class.forName()这个方法会立即进行类的初始化,直接输出了Initial3中静态代码块中的内容。36行调用了Initable3.staticNonFinal,这里我们要注意在34行已经对Initial3这个类进行了初始化,而且JVM规定一个类加载器下一个类只能初始化一次,所以这里只是输出了Initable3.staticNonFinal的值,而并没有再次打印Initial3中静态代码块的内容。

另外附上java三个类加载器的关系以及个子加载的范围类型:

Java在需要使用类的时候,才会将类加载,Java的类载入是由类加载器(Class loader)来达到的,预设上,在程序启动之后,主要会有三个类加载器:Bootstrap Loader、ExtClassLoader与AppClassLoader。

Bootstrap Loader是由C++撰写而成,预设上它负责搜寻JRE所在目录的classes或lib目录下的.jar档案中(例如rt.jar)是否有指定的类别 并加载(实际上是由系统参数sun.boot.class.path指定);预设上ExtClassLoader负责搜寻JRE所在目录的lib/ext 目录下的classes或.jar中是否有指定的类别并加载(实际上是由系统参数java.ext.dirs指定);AppClassLoader则搜寻 Classpath中是否有指定的classes并加载(由系统参数java.class.path指定)。

Bootstrap Loader会在JVM启动之后载入,之后它会载入ExtClassLoader并将ExtClassLoader的parent设为Bootstrap Loader,然后BootstrapLoader再加载AppClassLoader,并将AppClassLoader的parent设定为 ExtClassLoader。

在加载类别时,每个类加载器会先将加载类别的任务交由其parent,如果parent找不到,才由自己负责加载,如果自己也找不到,就会丢出 NoClassDefFoundError。

每一个类别被载入后,都会有一个Class的实例来代表它,每个Class的实例都会记得是哪个ClassLoader加载它的,可以由Class的getClassLoader()取得加载该类的ClassLoader。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值