java程序运行分析

使用工具:Eclipse Stardard 4.32版本(window7环境)

今天我们通过一个及其简单的例子来分析一个java程序是如何在我们的及其上跑起来的。部分内容是参考其他人的,会在参考的地方注明。

我的测试代码部分如下:

public class Test {

public static void main(String[] args) {

new Test();

      Int test =1;//只是为了测试方便,去掉IO部分的分析

}

}

就是这么简单的代码,如何在我们的机器上跑起来,确实是需要一件很折磨人的事情

首先我们了解一下,我们的Eclipse是如何启动的.



参考书籍:Java编程思想(第四版)      深入理解java虚拟机(周志明著)

在这里我就不重复罗嗦详细的原理,因为那不是我想要说的,我想分析的是最简单的java程序是如何跑起来

的,而非一个exe是如何跑起来的

第一个步骤:

我们都会去双击一个eclipse.exe程序,然后会出现启动页面.

这个过程是会去读取相同目录下的配置文件eclipse.ini,读取出eclipse所需要运行的一些参数,

比如我们安装的jdk/jre路径在哪,在jdk1.5版本之前,在eclipse.ini中我们会发现-vm参数,但是后

续版本却没有该参数了,原因是后续版本的java程序不一定运行在sun的hotspot虚拟机中,也可能

运行其他虚拟,因此在后续版本中取消了该参数,然后会读取到启动eclipse程序的jar包startup.jar

我们通过解压\plugins\org.eclipse.equinox.launcher_1.3.0.v20130327-1440.jar(不同版本后面的

参数会不一样)会发现启动的Main类,然后通过Main类的静态方法main(String args)就会调用一系

列与本机有关的参数,通过JNI接口来调用本地方法启动我们的Eclipse了如果你感兴趣,可以读取上

面的四个参考博客,讲述的非常详细.

第二个步骤:

我们会运行我们编写好的代码,虽然我们只是在Eclipse点击了一个运行按钮,但是Eclipse却是

调用了我们安装在本机的jdk/bin中的工具,去启动我们的JVM,然后加载我们的类调入内存

中执行,因此我们所需要分析的就是这个步骤,究竟我们写好java代码,是怎么运行起来的.而我

们能做的就是通过调试跟踪源码的走向,来分析,JVM究竟做了哪些封装好的事情.

第三个步骤:

好了,我们开始吧.

我们在new Test()处设置好断点,然后右键以debug模式运行,会出来下面图片

 


我们可以在左上角看到,我们在我们的Test程序运行于本机上的56056号端口中,同时就我们的主线程运行在main方法中,这也佐证了,我们编写的main函数就是整个程序的主线程,而同时是Eclipse调用了我们安装在"C:\Program Files\Java\jre7\bin\javaw.exe" 

下的JVM,如果我们此时在上面的路径中,如下图标注的地方:

 

会发现我们的一些JVM参数信息,如下图:

 

其中Command Line中就是我们启动的JVM部分参数,如果需要详细参数的话,可以利用jdk/bin中的jConsole.exe来调出JVM系统分析工具,运行如下图:

 

从途中我们可以看出,我们一个启动了三个进程,其中一个是Jconsole.exe本身,第二个是我们的Eclipse自身,第三个则是我们自己编写的Test类,我们在这里连接选择Test,进入之后我们就可以发现一个分析控制台了,我们点击进入VM概要,可以发现更多的JVM参数,如图:

 

从上图中我们可以看出,其实我们在Eclipse中属性中打开的的Command Line中参数只是上图最下侧部分的一部分数据而已.我们暂时不关注这些东西,我们只是去分析我们的程序是怎么运行起来的.

继续回到Eclipse中,我们通过单步调入的方式逐行去调试我们的程序,看我们的程序究竟是如何进入内存的。

 

从上面图中看到,在Test()中调用了init()方法,但是我们明明没有写该方法啊,我们注意一下的话就发现,init被<>包起来的,其实这是JVM去调用的,整个方法的作用就是在一个类进行对象实例化时调用的,Java编译器会为它的每一个类都至少生成一个实例初始化方法。在Class文件中,被称为"<init>",详情请参考深入理解java虚拟机一书,经过上述测试发现,当我们绕过IO部分得话,程序会很快退出该线程,因此为了更加详细一些,我还是增加了IO部分,因此代码部分就变更为:

Demo.java

package com.test;

public class Demo {

public void say(){

sayHello();

}

private void sayHello() {

System.out.println("hello");

}

}

Test.java

package com.test;

public class Test {

public static void main(String[] args) {

Demo demo = new Demo();

demo.say();

}

}

 

这样以来,我们就可以稍微复杂但分析这一情况。当进入debug模式后,我们会发现更我们刚才的情况不同了.

 

我们的Test类在初始化的时候,会出现一个ClassNotFoundException的异常提示,其实这是JVM在加载我们的类的时候一个验证阶段,为了安全,JVM做了大量的验证来保证我们编写的类文件必须是合法的.而是否存在该类便属于其中一个因素,当JVM发现我们编写的类却是存在的时候,就会去实例话它,但是在这之前,JVM还是需要做一些准备,初始化它的父类或接口,这是保证该类数据完整性的验证,因为在依赖倒置原则中,我们推荐每个类尽量有自己的类或者接口,而JVM也是认同这一点,但是不过我们所编写的类是否显示的继承于某一个类,都会默认去继承Object类的,因此,JVM接下来自然而然会去初始化我们的Object类,当JVM经过一系列的class文件的验证之后,才会通过不同的类加载器来加载我们的编写的类文件

 


我们可以看到不同的加载器最后才到达我们的Eclipse中的类加载器,最后才真正到了能够加载我们编写的类的加载器了ClassLoader

 

至此,程序将我们的Test文件通过编译解析到了内存中,

 

然后程序会继续随着程序的执行去实例化Demo类,至此,整个分析就结束了,鉴于第一次尝试分析,因此结果比较草率,后续我会整理一下,把整个思路顺一下,写一个完整点稍微复杂点的分析,整理成pdf以便下载。

 

java程序运行分析,古老的榕树,5-wow.com

郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。