java程序的加载过程

原创 2013年12月03日 08:56:09
  1. public class StaticTest {  
  2.     public static int k=0;  
  3.     public static StaticTest s1=new StaticTest("s1");  
  4.     public static StaticTest s2=new StaticTest("s2");  
  5.     public static int i=print("i");  
  6.     public static int n=99;  
  7.     public int j=print("j");  
  8.       
  9.     {  
  10.         print("构造块");  
  11.     }  
  12.       
  13.     static  
  14.     {  
  15.         print("静态块");  
  16.     }  
  17.       
  18.     public static int print(String s)  
  19.     {  
  20.         System.out.println(++k+":"+s+"\ti="+i+"\tn="+n);  
  21.         ++n;  
  22.         return ++i;  
  23.     }  
  24.       
  25.     public StaticTest(String s)  
  26.     {  
  27.         System.out.println(++k+":"+s+"\ti="+i+"\tn="+n);  
  28.         ++i;  
  29.         ++n;  
  30.     }  
  31.   
  32.     public static void main(String[] args) {  
  33.         new StaticTest("init");  
  34.     }  
  35. }  

首先给出代码输出:

  1. 1:j i=0 n=0  
  2. 2:构造块   i=1 n=1  
  3. 3:s1    i=2 n=2  
  4. 4:j i=3 n=3  
  5. 5:构造块   i=4 n=4  
  6. 6:s2    i=5 n=5  
  7. 7:i i=6 n=6  
  8. 8:静态块   i=7 n=99  
  9. 9:j i=8 n=100  
  10. 10:构造块  i=9 n=101  
  11. 11:init i=10    n=102  

没想到只是创建了一个对象,居然执行了这么多语句!下面我们逐条分析每条输出语句。

首先我们需要对java程序的加载过程有个大概的了解:第一执行类中的静态代码,包括静态成员变量的初始化和静态语句块的执行;第二执行类中的非静态代码,包括非静态成员变量的初始化和非静态语句块的执行,最后执行构造函数。在继承的情况下,会首先执行父类的静态代码,然后执行子类的静态代码;之后执行父类的非静态代码和构造函数;最后执行子类的非静态代码和构造函数。用图表示如下:

第一条语句打印的是j相关的内容,所以执行了第7行代码。很明显该行代码执行的是非静态变量的赋值操作,这似乎不符合上述java程序加载规则。我们按照前述规则执行一下代码,首先会执行静态变量k的赋值,然后创建该类的一个静态实例。这时我们就会发现,第一条打印语句可能和该类的这个静态实例对象有关。我们尝试着创建这个静态实例,这时的程序加载过程又变为上述标准的加载过程:首先执行静态代码,然后非静态,最后构造函数。由于静态代码的执行是按代码的先后顺序进行,所以创建该静态实例时只有第一个静态变量k会赋值,后面的静态变量和静态语句块还都不存在;之后执行非静态代码,第一句非静态代码即是代码第7行的变量j赋值。这就解释了为什么第一条打印语句会是第7行的代码。同时这也解释了第二和第三条打印,第二条打印语句执行非静态代码,执行之后就调用构造函数创建实例对象s1。

同理,第4到6条打印语句是在创建静态实例对象s2时执行的。

在完成两个静态实例对象的创建后,下面要执行静态变量i的赋值,这就是第7条打印语句。后面还会对静态成员变量n赋值。之后是执行静态语句块,打印第8行语句。执行完静态代码部分后,接下来要执行非静态代码部分,按照写代码的前后顺序先为j赋值,然后执行非静态语句块,这就是第9和10行的打印语句。在执行完上面的所有步骤之后,开始执行类的构造函数创建对象,这就是第11行打印语句。

通过上面的分析我们发现:上述代码的执行顺序依旧符合一开始说明的java程序加载过程。只是由于有两个该类的静态实例变量,导致打印语句的复杂化。在这种情况下,打印过程类似于一个递归,每一次递归都按照标准的加载过程执行。

该代码一开始给人很多疑问,感觉运行过程中会抛出各种异常,但是代码却神奇地打印出了11条语句,的确让人吃惊。第一个疑问是该类内部有一个该类自身的静态对象,是否会导致循环递归。大家可以尝试一下,将代码第3或4行的static去掉,然后运行程序,就会提示StackOverflowError异常。为啥静态对象不会导致栈溢出,而非静态对象就会溢出?这是因为静态成员变量属于类所有,所有的类对象共享该静态成员变量,也即该静态成员变量只有一份,所以在递归的过程中,当发现正在创建该静态变量时,系统不会再去创建该变量,所以不会递归。但是如果是非静态对象,在递归的过程中,每次遇到该new语句都会再次创建一个新的对象,导致栈溢出。

该代码中另一个疑问是变量i的初值。在静态函数print中会打印i的值,但是在打印的时候变量i可能还没有定义(前6行打印语句都没有定义变量i)。程序居然也给通过了,这说明静态变量的声明会在初始化之前完成,并赋初值0。

类加载的顺序:
1.加载静态成员/代码块:
先递归地加载父类的静态成员/代码块(Object的最先);再依次加载到本类的静态成员。
同一个类里的静态成员/代码块,按写代码的顺序加载。
如果其间调用静态方法,则调用时会先运行静态方法,再继续加载。同一个类里调用静态方法时,可以不理会写代码的顺序。
调用父类的静态成员,可以像调用自己的一样;但调用其子类的静态成员,必须使用“子类名.成员名”来调用。
2.加载非静态成员/代码块:(实例块在创建对象时才会被加载。而静态成员在不创建对象时可以加载)
先递归地加载父类的非静态成员/代码块(Object的最先);再依次加载到本类的非静态成员。
同一个类里的非静态成员/代码块,按写代码的顺序加载。同一个类里调用方法时,可以不理会写代码的顺序。
但调用属性时,必须注意加载顺序。一般编译不通过,如果能在加载前调用,值为默认初始值(如:null 或者 0)。
调用父类的非静态成员(private 除外),也可以像调用自己的一样。
3.调用构造方法:
先递归地调用父类的构造方法(Object的最先);默认调用父类空参的,也可在第一行写明调用父类某个带参的。
再依次到本类的构造方法;构造方法内,也可在第一行写明调用某个本类其它的构造方法。
注意:如果加载时遇到 override 的成员,可看作是所需创建的类型赋值给当前类型。
其调用按多态用法:只有非静态方法有多态;而静态方法、静态属性、非静态属性都没有多态。
假设子类override父类的所有成员,包括静态成员、非静态属性和非静态方法。
由于构造子类时会先构造父类;而构造父类时,其所用的静态成员和非静态属性是父类的,但非静态方法却是子类的;
由于构造父类时,子类并未加载;如果此时所调用的非静态方法里有成员,则这个成员是子类的,且非静态属性是默认初始值的。


java程序执行过程中动态加载jar包

最近做一个远程接口测试系统,遇到了动态加载jar包的问题,如将用户上传的jar包动态加载后调用远程接口的方法,特将例子记录下来以便查找。...
  • baokx
  • baokx
  • 2015年09月16日 18:27
  • 1321

java程序加载过程

未完待续注意:Android虚拟机和我们常见JVM虚拟机是不同的加载过程1当遇到Main方法的时候会自动压栈2 .class字节码 文件首先被加载到方法区,占用一个空间, 在常量池,创建一个....
  • ccj659
  • ccj659
  • 2017年02月28日 21:08
  • 327

举例说明一个 java程序的加载,初始化以及运行过程

源代码如下: public class ExA { private static ExA a = new ExA(); static { System.out.prin...
  • zyf4005
  • zyf4005
  • 2016年05月03日 19:21
  • 243

java程序的加载过程

昨天笔试阿里有个求java程序加载过程的题目很是复杂,回来研究了好久才有点明白,整理一下。原题代码如下,判断输出: public class StaticTest { public static ...

Java--Java程序的加载过程

java程序在执行过程中,类,对象以及它们成员加载、初始化的顺序如下: 1、首先加载要创建对象的类及其直接与间接父类。 2、在类被加载的同时会将静态成员进行加载,主要包括静态成员变量的初始化,静态...
  • zlQQhs
  • zlQQhs
  • 2013年07月21日 01:54
  • 735

java程序中加载动态链接库文件

  • 2016年01月21日 11:45
  • 20KB
  • 下载

java程序中动态加载jar包并执行

参考了几篇文章 http://www.cnblogs.com/flyingzl/articles/3139028.html 这个写的比较简单些, 加载jar包的类URLClassLoader中使用的...

命令行编译运行Java程序时,加载指定目录中的Jar包

原文地址:点击打开链接 命令行编译运行Java程序时,加载指定目录中的Jar包: 解决方案一: [java] view plain copy     编译:javac -Dj...

命令行编译运行Java程序时,加载指定目录中的Jar包

命令行编译运行Java程序时,加载指定目录中的Jar包 [java] view plain copy   编译:javac -Djava.ext.dirs=./lib Test.j...

浅谈Java程序加载机制

大家都知道Java程序在编译时,会将源文件(.java)装换成字节码文件(.class),但是class文件并不是本地的可执行文件。运行Java程序时,首先需要运行Java虚拟机(JVM),然后再把c...
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:java程序的加载过程
举报原因:
原因补充:

(最多只允许输入30个字)