在jvm虚拟机中,java 的方法到底是php 传值 传址还是传址

JVM虚拟机的启动流程原理——JVM之一
JVM是Java程序运行的环境,同时是一个操作系统的一个应用程序进程,因此它有自己的生命周期,也有自己的代码和数据空间.
JVM体系主要是两个JVM的内部体系结构分为三个子系统和两大组件,分别是:类装载器(ClassLoader)子系统、执行引擎子系统和GC子系统,组件是内存运行数据区域和本地接口。
JVM工作原理和特点主要是指操作系统装入JVM,是通过jdk中Java.exe来完成,通过下面4步来完成JVM环境.
1.创建JVM装载环境和配置
2.装载JVM.dll
3.初始化JVM.dll并挂界到JNIENV(JNI调用接口)实例
4.调用JNIEnv实例装载并处理class类。
一.JVM装入环境,JVM提供的方式是操作系统的动态连接文件.
既然是文件那就一个装入路径的问题,Java是怎么找这个路径的呢?
当你在调用Java test的时候,操作系统会在path下在你的Java.exe程序,Java.exe就通过下面一个过程来确定JVM的路径和相关的参数配置了.下面基于Windows的实现的分析:
1、首先查找jre路径,Java是通过GetApplicationHome api来获得当前的Java.exe绝对路径,c:\j2sdk1.4.2_09\bin\Java.exe,那么它会截取到绝对路径c:\j2sdk1.4.2_09\,判断c:\j2sdk1.4.2_09\bin\Java.dll文件是否存在,
  如果存在就把c:\j2sdk1.4.2_09\作为jre路径,
  如果不存在则判断c:\j2sdk1.4.2_09\jre\bin\Java.dll是否存在,如果存在这c:\j2sdk1.4.2_09\jre作为jre路径.如果不存在调用GetPublicJREHome查HKEY_LOCAL_MACHINE\Software\JavaSoft\Java Runtime Environment\“当前JRE版本号”\JavaHome的路径为jre路径。
2、然后装载JVM.cfg文件JRE路径+\lib+\ARCH(CPU构架)+\JVM.cfg ,ARCH(CPU构架)的判断是通过Java_md.c中GetArch函数判断的,该函数中windows平台只有两种情况:WIN64的‘ia64’,其他情况都为‘i386’。以我的为例:C:\j2sdk1.4.2_09\jre\lib\i386\JVM.cfg.主要的内容如下:
-client KNOWN
-server KNOWN
-hotspot ALIASED_TO -client
-classic WARN
-native ERROR
-green ERROR
在我们的jdk目录中jre\bin\server和jre\bin\client都有JVM.dll文件存在,而Java正是通过JVM.cfg配置文件来管理这些不同版本的JVM.dll的.通过文件我们可以定义目前jdk中支持那些JVM,前面部分(client)是JVM名称,后面是参数,KNOWN表示JVM存在,ALIASED_TO表示给别的JVM取一个别名,WARN表示不存在时找一个JVM替代,ERROR表示不存在抛出异常.
  在运行Java XXX是,Java.exe会通过CheckJVMType来检查当前的JVM类型,Java可以通过两种参数的方式来指定具体的JVM类型,一种按照JVM.cfg文件中的JVM名称指定,第二种方法是直接指定,它们执行的方法分别是“Java -J”、“Java -XXaltJVM=”或“Java -J-XXaltJVM=”。如果是第一种参数传递方式,CheckJVMType函数会取参数‘-J’后面的JVM名称,然后从已知的JVM配置参数中查找如果找到同名的则去掉该JVM名称前的‘-’直接返回该值;而第二种方法,会直接返回“-XXaltJVM=”或“-J-XXaltJVM=”后面的JVM类型名称;如果在运行Java时未指定上面两种方法中的任一一种参数,CheckJVMType会取配置文件中第一个配置中的JVM名称,去掉名称前面的‘-’返回该值。CheckJVMType函数的这个返回值会在下面的函数中汇同jre路径组合成JVM.dll的绝对路径。如果没有指定这会使用JVM.cfg中第一个定义的JVM.可以通过set _Java_LAUNCHER_DEBUG=1在控制台上测试.
最后获得JVM.dll的路径,JRE路径+\bin+\JVM类型字符串+\JVM.dll就是JVM的文件路径了,但是如果在调用Java程序时用-XXaltJVM=参数指定的路径path,就直接用path+\JVM.dll文件做为JVM.dll的文件路径.
二:装载JVM.dll
通过第一步已经找到了JVM的路径,Java通过LoadJavaVM来装入JVM.dll文件.装入工作很简单就是调用Windows API函数:
LoadLibrary装载JVM.dll动态连接库.然后把JVM.dll中的导出函数JNI_CreateJavaVM和JNI_GetDefaultJavaVMInitArgs挂接到InvocationFunctions变量的CreateJavaVM和GetDefaultJavaVMInitArgs函数指针变量上。JVM.dll的装载工作宣告完成。
三:初始化JVM,获得本地调用接口,这样就可以在Java中调用JVM的函数了.调用InvocationFunctions-&CreateJavaVM也就是JVM中JNI_CreateJavaVM方法获得JNIEnv结构的实例.
四:运行Java程序.
Java程序有两种方式一种是jar包,一种是.class:
运行jar,Java -jar XXX.jar运行的时候,Java.exe调用GetMainClassName函数,该函数先获得JNIEnv实例,然后调用Java类Java.util.jar.JarFileJNIEnv中方法getManifest()并从返回的Manifest对象中取getAttributes(“Main-Class”)的值即jar包中文件:META-INF/MANIFEST.MF指定的Main-Class的主类名作为运行的主类。
之后会调用Java.c中LoadClass方法装载该主类(使用JNIEnv实例的FindClass)。
然后调用JNIEnv实例的GetStaticMethodID方法查找装载的class主类中“public static void main(String
是本站开发的一款收藏记录创意分享 ,开启高效之门的在线应用。点击: 收藏此文
你可能对这些文章感兴趣.
Copyright & 2014 java面试题缓缓琢磨JVM——恭喜JavaEye重新开张
慢慢琢磨JVM——恭喜JavaEye重新开张
JVM是我们Javaer的最基本功底了,刚开始学Java的时候,一般都是从“Hello World”开始的,然后会写个复杂点class,然后再找一些开源框架,比如Spring,Hibernate等等,再然后就开发企业级的应用,比如网站、企业内部应用、实时交易系统等等,直到某一天突然发现做的系统咋就这么慢呢,而且时不时还来个内存溢出什么的,今天是交易系统报了StackOverflowError,明天是网站系统报了个OutOfMemoryError,这种错误又很难重现,只有分析Javacore和dump文件,运气好点还能分析出个结果,运行遭的点,就直接去庙里烧香吧!每天接客户的电话都是战战兢兢的,生怕再出什么幺蛾子了。我想Java做的久一点的都有这样的经历,那这些问题的最终根结是在哪呢?—— JVM。
JVM全称是Java Virtual Machine,Java虚拟机,也就是在计算机上再虚拟一个计算机,这和我们使用 VMWare不一样,那个虚拟的东西你是可以看到的,这个JVM你是看不到的,它存在内存中。我们知道计算机的基本构成是:运算器、控制器、存储器、输入和输出设备,那这个JVM也是有这成套的元素,运算器是当然是交给硬件CPU还处理了,只是为了适应“一次编译,随处运行”的情况,需要做一个翻译动作,于是就用了JVM自己的命令集,这与汇编的命令集有点类似,每一种汇编命令集针对一个系列的CPU,比如8086系列的汇编也是可以用在8088上的,但是就不能跑在8051上,而JVM的命令集则是可以到处运行的,因为JVM做了翻译,根据不同的CPU,翻译成不同的机器语言。
JVM中我们最需要深入理解的就是它的存储部分,存储?硬盘?NO,NO, JVM是一个内存中的虚拟机,那它的存储就是内存了,我们写的所有类、常量、变量、方法都在内存中,这决定着我们程序运行的是否健壮、是否高效,接下来的部分就是重点介绍之。
2 JVM的组成部分
我们先把JVM这个虚拟机画出来,如下图所示:
从这个图中可以看到,JVM是运行在操作系统之上的,它与硬件没有直接的交互。我们再来看下JVM有哪些组成部分,如下图所示:
该图参考了网上广为流传的JVM构成图,大家看这个图,整个JVM分为四部分:
Class Loader 类加载器
类加载器的作用是加载类文件到内存,比如编写一个HelloWord.java程序,然后通过javac编译成class文件,那怎么才能加载到内存中被执行呢?Class Loader承担的就是这个责任,那不可能随便建立一个.class文件就能被加载的,Class Loader加载的class文件是有格式要求,在《JVM Specification》中式这样定义Class文件的结构:
ClassFile {
constant_pool_
constant_pool[constant_pool_count-1];
interfaces_
interfaces[interfaces_count];
field_info
fields[fields_count];
method_info
methods[methods_count];
attributes_
attribute_info
attributes[attributes_count];
需要详细了解的话,可以仔细阅读《JVM Specification》的第四章“The class File Format”,这里不再详细说明。
友情提示:Class
Loader只管加载,只要符合文件结构就加载,至于说能不能运行,则不是它负责的,那是由Execution
Engine负责的。
Execution Engine 执行引擎
执行引擎也叫做解释器(Interpreter),负责解释命令,提交操作系统执行。
Native Interface本地接口
本地接口的作用是融合不同的编程语言为Java所用,它的初衷是融合C/C++程序,Java诞生的时候是C/C++横行的时候,要想立足,必须有一个聪明的、睿智的调用C/C++程序,于是就在内存中专门开辟了一块区域处理标记为native的代码,它的具体做法是Native Method Stack中登记native方法,在Execution Engine执行时加载native libraies。目前该方法使用的是越来越少了,除非是与硬件有关的应用,比如通过Java程序驱动打印机,或者Java系统管理生产设备,在企业级应用中已经比较少见,因为现在的异构领域间的通信很发达,比如可以使用Socket通信,也可以使用Web Service等等,不多做介绍。
Runtime data area运行数据区
运行数据区是整个JVM的重点。我们所有写的程序都被加载到这里,之后才开始运行,Java生态系统如此的繁荣,得益于该区域的优良自治,下一章节详细介绍之。
整个JVM框架由加载器加载文件,然后执行器在内存中处理数据,需要与异构系统交互是可以通过本地接口进行,瞧,一个完整的系统诞生了!
2 JVM的内存管理
所有的数据和程序都是在运行数据区存放,它包括以下几部分:
栈也叫栈内存,是Java程序的运行区,是在线程创建时创建,它的生命期是跟随线程的生命期,线程结束栈内存也就释放,对于栈来说不存在垃圾回收问题,只要线程一结束,该栈就Over。问题出来了:栈中存的是那些数据呢?又什么是格式呢?
栈中的数据都是以栈帧(Stack Frame)的格式存在,栈帧是一个内存区块,是一个数据集,是一个有关方法(Method)和运行期数据的数据集,当一个方法A被调用时就产生了一个栈帧F1,并被压入到栈中,A方法又调用了B方法,于是产生栈帧F2也被压入栈,执行完毕后,先弹出F2栈帧,再弹出F1栈帧,遵循“先进后出”原则。
那栈帧中到底存在着什么数据呢?栈帧中主要保存3类数据:本地变量(Local Variables),包括输入参数和输出参数以及方法内的变量;栈操作(Operand Stack),记录出栈、入栈的操作;栈帧数据(Frame Data),包括类文件、方法等等。光说比较枯燥,我们画个图来理解一下Java栈,如下图所示:
图示在一个栈中有两个栈帧,栈帧2是最先被调用的方法,先入栈,然后方法2又调用了方法1,栈帧1处于栈顶的位置,栈帧2处于栈底,执行完毕后,依次弹出栈帧1和栈帧2,线程结束,栈释放。
Heap 堆内存
一个JVM实例只存在一个堆类存,堆内存的大小是可以调节的。类加载器读取了类文件后,需要把类、方法、常变量放到堆内存中,以方便执行器执行,堆内存分为三部分:
Permanent Space 永久存储区
永久存储区是一个常驻内存区域,用于存放JDK自身所携带的Class,Interface的元数据,也就是说它存储的是运行环境必须的类信息,被装载进此区域的数据是不会被垃圾回收器回收掉的,关闭JVM才会释放此区域所占用的内存。
Young Generation Space 新生区
新生区是类的诞生、成长、消亡的区域,一个类在这里产生,应用,最后被垃圾回收器收集,结束生命。新生区又分为两部分:
伊甸区(Eden space)和幸存者区(Survivor pace),所有的类都是在伊甸区被new出来的。幸存区有两个: 0区(Survivor
0 space)和1区(Survivor 1 space)。当伊甸园的空间用完时,程序又需要创建对象,JVM的垃圾回收器将对伊甸园区进行垃圾回收,将伊甸园区中的不再被其他对象所引用的对象进行销毁。然后将伊甸园中的剩余对象移动到幸存0区。若幸存0区也满了,再对该区进行垃圾回收,然后移动到1区。那如果1区也满了呢?再移动到养老区。
Tenure generation space养老区
养老区用于保存从新生区筛选出来的JAVA对象,一般池对象都在这个区域活跃。
三个区的示意图如下:
Method Area 方法区
方法区是被所有线程共享,该区域保存所有字段和方法字节码,以及一些特殊方法如构造函数,接口代码也在此定义。
PC Register 程序计数器
每个线程都有一个程序计数器,就是一个指针,指向方法区中的方法字节码,由执行引擎读取下一条指令。
Native Method Stack 本地方法栈
3 JVM相关问题
问:堆和栈有什么区别
答:堆是存放对象的,但是对象内的临时变量是存在栈内存中,如例子中的methodVar是在运行期存放到栈中的。
栈是跟随线程的,有线程就有栈,堆是跟随JVM的,有JVM就有堆内存。
问:堆内存中到底存在着什么东西?
答:对象,包括对象变量以及对象方法。
问:类变量和实例变量有什么区别?
答:静态变量是类变量,非静态变量是实例变量,直白的说,有static修饰的变量是静态变量,没有static修饰的变量是实例变量。静态变量存在方法区中,实例变量存在堆内存中。
问:我听说类变量是在JVM启动时就初始化好的,和你这说的不同呀!
答:那你是道听途说,信我的,没错。
问:Java的方法(函数)到底是传值还是传址?
答:都不是,是以传值的方式传递地址,具体的说原生数据类型传递的值,引用类型传递的地址。对于原始数据类型,JVM的处理方法是从Method Area或Heap中拷贝到Stack,然后运行frame中的方法,运行完毕后再把变量指拷贝回去。
问:为什么会产生OutOfMemory产生?
答:一句话:Heap内存中没有足够的可用内存了。这句话要好好理解,不是说Heap没有内存了,是说新申请内存的对象大于Heap空闲内存,比如现在Heap还空闲1M,但是新申请的内存需要1.1M,于是就会报OutOfMemory了,可能以后的对象申请的内存都只要0.9M,于是就只出现一次OutOfMemory,GC也正常了,看起来像偶发事件,就是这么回事。
但如果此时GC没有回收就会产生挂起情况,系统不响应了。
问:我产生的对象不多呀,为什么还会产生OutOfMemory?
答:你继承层次忒多了,Heap中
产生的对象是先产生
父类,然后才产生子类,明白不?
问:OutOfMemory错误分几种?
答:分两种,分别是“OutOfMemoryError:java heap size”和”OutOfMemoryError: PermGen space”,两种都是内存溢出,heap size是说申请不到新的内存了,这个很常见,检查应用或调整堆内存大小。
“PermGen space”是因为永久存储区满了,这个也很常见,一般在热发布的环境中出现,是因为每次发布应用系统都不重启,久而久之永久存储区中的死对象太多导致新对象无法申请内存,一般重新启动一下即可。
问:为什么会产生StackOverflowError?
答:因为一个线程把Stack内存全部耗尽了,一般是递归函数造成的。
问:一个机器上可以看多个JVM吗?JVM之间可以互访吗?
答:可以多个JVM,只要机器承受得了。JVM之间是不可以互访,你不能在A-JVM中访问B-JVM的Heap内存,这是不可能的。在以前老版本的JVM中,会出现A-JVM Crack后影响到B-JVM,现在版本非常少见。
问:为什么Java要采用垃圾回收机制,而不采用C/C++的显式内存管理?
答:为了简单,内存管理不是每个程序员都能折腾好的。
问:为什么你没有详细介绍垃圾回收机制?
答:垃圾回收机制每个JVM都不同,JVM Specification只是定义了要自动释放内存,也就是说它只定义了垃圾回收的抽象方法,具体怎么实现各个厂商都不同,算法各异,这东西实在没必要深入。
问:JVM中到底哪些区域是共享的?哪些是私有的?
答:Heap和Method Area是共享的,其他都是私有的,
问:什么是JIT,你怎么没说?
答:JIT是指Just In Time,有的文档把JIT作为JVM的一个部件来介绍,有的是作为执行引擎的一部分来介绍,这都能理解。Java刚诞生的时候是一个解释性语言,别嘘,即使编译成了字节码(byte code)也是针对JVM的,它需要再次翻译成原生代码(native code)才能被机器执行,于是效率的担忧就提出来了。Sun为了解决该问题提出了一套新的机制,好,你想编译成原生代码,没问题,我在JVM上提供一个工具,把字节码编译成原生码,下次你来访问的时候直接访问原生码就成了,于是JIT就诞生了,就这么回事。
问:JVM还有哪些部分是你没有提到的?
答:JVM是一个异常复杂的东西,写一本砖头书都不为过,还有几个要说明的:
常量池(constant pool):按照顺序存放程序中的常量,并且进行索引编号的区域。比如int i
=100,这个100就放在常量池中。
安全管理器(Security Manager):提供Java运行期的安全控制,防止恶意攻击,比如指定读取文件,写入文件权限,网络访问,创建进程等等,Class Loader在Security Manager认证通过后才能加载class文件的。
方法索引表(Methods
table),记录的是每个method的地址信息,Stack和Heap中的地址指针其实是指向Methods table地址。
问:为什么不建议在程序中显式的生命System.gc()?
答:因为显式声明是做堆内存全扫描,也就是Full GC,是需要停止所有的活动的(Stop
The World Collection),你的应用能承受这个吗?
问:JVM有哪些调整参数?
答:非常多,自己去找,堆内存、栈内存的大小都可以定义,甚至是堆内存的三个部分、新生代的各个比例都能调整。
拷贝过来后格式都变了,就附上PDF文件。
欢迎重拍,别手下留情,我喜欢~~~~
学习了,收获不小,但不知是转帖还是原创绝对是原创!不过,无论是转帖还是原创,你们不都受益了么?
问:我听说类变量是在JVM启动时就初始化好的,和你这说的不同呀!答:那你是道听途说,信我的,没错。class test{
……main(String[] args){& sysout(i); }}难道它会报错?JVM启动的时候怎么可能知道有哪些类,哪个类中有哪些变量呢?所以说JVM启动时就初始化某个类变量是不可能的。在JVM启动之后,在执行main方法时(这里会有一个main线程),首先会去初始化test类以及该类的static field 包括static block。这个时候才是类变量的初始化。我认为是这样的
问:堆和栈有什么区别答:堆是存放对象的,但是对象内的临时变量是存在栈内存中,如例子中的methodVar是在运行期存放到栈中的。栈是跟随线程的,有线程就有栈,堆是跟随JVM的,有JVM就有堆内存。大大请教对象内的临时变量是什么来的?倔强的小强 写道问:Java的方法(函数)到底是传值还是传址?答:都不是,是以传值的方式传递地址,具体的说原生数据类型传递的值,引用类型传递的地址。对于原始数据类型,JVM的处理方法是从Method Area或Heap中拷贝到Stack,然后运行frame中的方法,运行完毕后再把变量指拷贝回去。大大请教红色部分是什么意思?能用图解释一下?倔强的小强 写道问:为什么你没有详细介绍垃圾回收机制?答:垃圾回收机制每个JVM都不同,JVM Specification只是定义了要自动释放内存,也就是说它只定义了垃圾回收的抽象方法,具体怎么实现各个厂商都不同,算法各异,这东西实在没必要深入。但是JVM Specification同样没有定义Java堆的构成方式,也就是说并不规定Java堆要划分为永久存储区、新生区、养老区。倔强的小强 写道Heap 堆内存一个JVM实例只存在一个堆类存,堆内存的大小是可以调节的。类加载器读取了类文件后,需要把类、方法、常变量放到堆内存中,以方便执行器执行,堆内存分为三部分:Permanent Space 永久存储区永久存储区是一个常驻内存区域,用于存放JDK自身所携带的Class,Interface的元数据,也就是说它存储的是运行环境必须的类信息,被装载进此区域的数据是不会被垃圾回收器回收掉的,关闭JVM才会释放此区域所占用的内存。Young Generation Space 新生区新生区是类的诞生、成长、消亡的区域,一个类在这里产生,应用,最后被垃圾回收器收集,结束生命。新生区又分为两部分: 伊甸区(Eden space)和幸存者区(Survivor pace),所有的类都是在伊甸区被new出来的。幸存区有两个: 0区(Survivor 0 space)和1区(Survivor 1 space)。当伊甸园的空间用完时,程序又需要创建对象,JVM的垃圾回收器将对伊甸园区进行垃圾回收,将伊甸园区中的不再被其他对象所引用的对象进行销毁。然后将伊甸园中的剩余对象移动到幸存0区。若幸存0区也满了,再对该区进行垃圾回收,然后移动到1区。那如果1区也满了呢?再移动到养老区。Tenure generation space养老区养老区用于保存从新生区筛选出来的JAVA对象,一般池对象都在这个区域活跃。大大可能已经知道,所谓永久存储区是用来实现JVM Specification的方法区的吧。倔强的小强 写道问:JVM还有哪些部分是你没有提到的?答:JVM是一个异常复杂的东西,写一本砖头书都不为过,还有几个要说明的:常量池(constant pool):按照顺序存放程序中的常量,并且进行索引编号的区域。比如int i =100,这个100就放在常量池中。安全管理器(Security Manager):提供Java运行期的安全控制,防止恶意攻击,比如指定读取文件,写入文件权限,网络访问,创建进程等等,Class Loader在Security Manager认证通过后才能加载class文件的。方法索引表(Methods table),记录的是每个method的地址信息,Stack和Heap中的地址指针其实是指向Methods table地址。大大请教红色部分是什么意思?倔强的小强 写道问:为什么不建议在程序中显式的生命System.gc()?答:因为显式声明是做堆内存全扫描,也就是Full GC,是需要停止所有的活动的(Stop& The World Collection),你的应用能承受这个吗?大大请看JRockit的文档引用This method can act differently on the JRockit JVM than on other JVMs. On the JRockit JVM, calling the System.gc() method results in a nursery collection if you are using a generational collector, and an old space collection if you are using a single generational collector.JVM Specification同样没有规定System.gc()要做Full GC,实现选择做minor GC或者完全忽略它(-XX:+DisableExplicitGC)都完全合理。
文章比较中肯,也比较全面,至于某些说的不是太准确的地方,应该是为了方便入门吧。那个i=100的,100可不是放在常量池里。jvm里short数值范围内的整数常量是有专门指令的(bipush),short范围之外的才会放在常量池里。那个如何解决OutofMemory的问题,当然是要分析你是怎么把内存(或其他资源)用光的。那个类何时初始化的问题,楼主说的没错,jvm是延迟加载的,你用到时才加载,你执行时才初始化。请教:“用到时才加载”是什么意思?能否举例说一下?还有,“jvm是延迟加载的”是始终都是延迟加载吗?是否有其他的加载方式?
文章评论 以下网友留言只代表其个人观点,不代表本网站的观点和立场。Java 中的finally你知多少? -
- ITeye技术网站
博客分类:
可不能小看这个简单的 finally,看似简单的问题背后,却隐藏了无数的玄机。接下来我就带您一步一步的揭开这个 finally 的神秘面纱。
首先来问大家一个问题:finally 语句块一定会执行吗?
很多人都认为 finally 语句块是肯定要执行的,其中也包括一些很有经验的 Java 程序员。可惜并不像大多人所认为的那样,对于这个问题,答案当然是否定的,我们先来看下面这个例子。
public class Test {
public static void main(String[] args) {
System.out
.println("return value of test(): " + test
());
public static int test() {
int i = 1;
if(i == 1)
System.out
.println("the previous statement of try block");
i = i / 0;
&&& System.out
.println("try block");
&&&& }finally {
&&&& System.out
.println("finally block");
清单 1 的执行结果如下:
the previous statement of try block
Exception in thread "main" java.lang.ArithmeticException: / by zero
at com.bj.charlie.Test.test(Test.java:15)
at com.bj.charlie.Test.main(Test.java:6)
另外,如果去掉上例中被注释的两条语句前的注释符,执行结果则是:
return value of test(): 0
在以上两种情况下,finally 语句块都没有执行,说明什么问题呢?只有与 finally 相对应的 try 语句块得到执行的情况下,finally 语句块才会执行。以上两种情况,都是在 try 语句块之前返回(return)或者抛出异常,所以 try 对应的 finally 语句块没有执行。
那好,即使与 finally 相对应的 try 语句块得到执行的情况下,finally 语句块一定会执行吗?不好意思,这次可能又让大家失望了,答案仍然是否定的。请看下面这个例子(清单 2)。
public class Test {
public static void main(String[] args) {
System.out
.println("return value of test(): " + test
());
public static int test() {
int i = 1;
System.out
.println("try block");
System.exit
(0);
}finally {
System.out
.println("finally block");
清单 2 的执行结果如下:
finally 语句块还是没有执行,为什么呢?因为我们在 try 语句块中执行了 System.exit (0) 语句,终止了 Java 虚拟机的运行。那有人说了,在一般的 Java 应用中基本上是不会调用这个 System.exit(0) 方法的。OK !没有问题,我们不调用 System.exit(0) 这个方法,那么 finally 语句块就一定会执行吗?
再一次让大家失望了,答案还是否定的。当一个线程在执行 try 语句块或者 catch 语句块时被打断(interrupted)或者被终止(killed),与其相对应的 finally 语句块可能不会执行。还有更极端的情况,就是在线程运行 try 语句块或者 catch 语句块时,突然死机或者断电,finally 语句块肯定不会执行了。可能有人认为死机、断电这些理由有些强词夺理,没有关系,我们只是为了说明这个问题。
finally 语句剖析
说了这么多,还是让我们拿出些有说服力的证据吧!还有什么证据比官方的文档更具说服力呢?让我们来看看官方网站上的《The Java Tutorials》中是怎样来描述 finally 语句块的吧!
以下位于 **** 之间的内容原封不动的摘自于《 The Java Tutorials 》文档。
*******************************************************************************
The finally Block
The finally block always executes when the try block exits. This ensures that the finally block is executed even if an unexpected exception occurs. But finally is useful for more than just exception handling — it allows the programmer to avoid having cleanup code accidentally bypassed by a return, continue, or break. Putting cleanup code in a finally block is always a good practice, even when no exceptions are anticipated.
Note:& If the JVM exits while the try or catch code is being executed, then the finally block may not execute. Likewise, if the thread executing the try or catch code is interrupted or killed, the finally block may not execute even though the application as a whole continues.
*******************************************************************************
请仔细阅读并认真体会一下以上两段英文,当你真正的理解了这两段英文的确切含义,你就可以非常自信的来回答“finally 语句块是否一定会执行?”这样的问题。看来,大多时候,并不是 Java 语言本身有多么高深,而是我们忽略了对基础知识的深入理解。
接下来,我们看一下 finally 语句块是怎样执行的。在排除了以上 finally 语句块不执行的情况后,finally 语句块就得保证要执行,既然 finally 语句块一定要执行,那么它和 try 语句块与 catch 语句块的执行顺序又是怎样的呢?还有,如果 try 语句块中有 return 语句,那么 finally 语句块是在 return 之前执行,还是在 return 之后执行呢?带着这样一些问题,我们还是以具体的案例来讲解。
关于 try、catch、finally 的执行顺序问题,我们还是来看看权威的论述吧!以下 **** 之间的内容摘自 Java 语言规范第四版(《 The Java(TM) Programming Language, Fourth Edition 》 )中对于 try,catch,和 finally 的描述。
*******************************************************************************
12.4. Try, catch, and finally
You catch exceptions by enclosing code in Try blocks. The basic syntax for a Try block is:
statements
} catch (exception_type1 identifier1) {
statements
} catch (exception_type2 identifier2) {
statements
} finally {
statements
where either at least one catch clause, or the finally clause, must be present. The body of the try statement is executed until either an exception is thrown or the body finishes successfully. If an exception is thrown, each catch clause is examined in turn, from first to last, to see whether the type of the exception object is assignable to the type declared in the catch. When an assignable catch clause is found, its block is executed with its identifier set to reference the exception object. No other catch clause will be executed. Any number of catch clauses, including zero, can be associated with a particular TRy as long as each clause catches a different type of exception. If no appropriate catch is found, the exception percolates out of the try statement into any outer try that might have a catch clause to handle it.
If a finally clause is present with a try, its code is executed after all other processing in the try is complete. This happens no matter how completion was achieved, whether normally, through an exception, or through a control flow statement such as return or break .
*******************************************************************************
上面这段文字的大体意思是说,不管 try 语句块正常结束还是异常结束,finally 语句块是保证要执行的。如果 try 语句块正常结束,那么在 try 语句块中的语句都执行完之后,再执行 finally 语句块。如果 try 中有控制转移语句(return、break、continue)呢?那 finally 语句块是在控制转移语句之前执行,还是之后执行呢?似乎从上面的描述中我们还看不出任何端倪,不要着急,后面的讲解中我们会分析这个问题。如果 try 语句块异常结束,应该先去相应的 catch 块做异常处理,然后执行 finally 语句块。同样的问题,如果 catch 语句块中包含控制转移语句呢? finally 语句块是在这些控制转移语句之前,还是之后执行呢?我们也会在后续讨论中提到。
其实,关于 try,catch,finally 的执行流程远非这么简单,有兴趣的读者可以参考 Java 语言规范第三版(《 The Java(TM) Language Specification, Third Edition 》 )中对于 Execution of try-catch-finally 的描述,非常复杂的一个流程。限于篇幅的原因,本文不做摘录,请感兴趣的读者自行阅读。
finally 语句示例说明
下面,我们先来看一个简单的例子(清单 3)。
public class Test {
public static void main(String[] args) {&
System.out
.println("try block");&
} finally {&
System.out
.println("finally block");&
清单 3 的执行结果为:
finally block
清单 3 说明 finally 语句块在 try 语句块中的 return 语句之前执行。我们再来看另一个例子(清单 4)。
public class Test {
public static void main(String[] args) {&
System.out
.println("reture value of test() : " + test
());
public static int test(){
int i = 1;
System.out
.println("try block");&
i = 1 / 0;
return 1;&
}catch (Exception e){
System.out
.println("exception block");
}finally {&
System.out
.println("finally block");&
清单 4 的执行结果为:
exception block
finally block
reture value of test() : 2
清单 4 说明了 finally 语句块在 catch 语句块中的 return 语句之前执行。
从上面的清单 3 和清单 4,我们可以看出,其实 finally 语句块是在 try 或者 catch 中的 return 语句之前执行的。更加一般的说法是,finally 语句块应该是在控制转移语句之前执行,控制转移语句除了 return 外,还有 break 和 continue。另外,throw 语句也属于控制转移语句。虽然 return、throw、break 和 continue 都是控制转移语句,但是它们之间是有区别的。其中 return 和 throw 把程序控制权转交给它们的调用者(invoker),而 break 和 continue 的控制权是在当前方法内转移。请大家先记住它们的区别,在后续的分析中我们还会谈到。
还是得来点有说服力的证据,下面这段摘自 Java 语言规范第四版(《 The Java(TM) Programming Language, Fourth Edition 》 ),请读者自己体会一下其含义。
*******************************************************************************
A finally clause can also be used to clean up for break , continue , and return , which is one reason you will sometimes see a try clause with no catch clauses. When any control transfer statement is executed, all relevant finally clauses are executed. There is no way to leave a try block without executing its finally clause.
*******************************************************************************
好了,看到这里,是不是有人认为自己已经掌握了 finally 的用法了?先别忙着下结论,我们再来看两个例子 – 清单 5 和清单 6。
public class Test {
public static void main(String[] args) {
&&&&&&& System.out
.println("return value of getValue(): " + getValue
());
public static int getValue() {
&&&&&&& try {
&&&&&&&&&&&&&&&& return 0;
&&&&&&& } finally {
&&&&&&&&&&&&&&&& return 1;
清单 5 的执行结果:
return value of getValue(): 1
public class Test {
public static void main(String[] args) {
&&&&&&& System.out
.println("return value of getValue(): " + getValue
());
public static int getValue() {
&&&&&&& int i = 1;
&&&&&&& try {
&&&&&&&&&&&&&&&&
&&&&&&& } finally {
&&&&&&&&&&&&&&&& i++;
清单 6 的执行结果:
return value of getValue(): 1
利用我们上面分析得出的结论:finally 语句块是在 try 或者 catch 中的 return 语句之前执行的。 由此,可以轻松的理解清单 5 的执行结果是 1。因为 finally 中的 return 1;语句要在 try 中的 return 0;语句之前执行,那么 finally 中的 return 1;语句执行后,把程序的控制权转交给了它的调用者 main()函数,并且返回值为 1。那为什么清单 6 的返回值不是 2,而是 1 呢?按照清单 5 的分析逻辑,finally 中的 i++;语句应该在 try 中的 return i;之前执行啊? i 的初始值为 1,那么执行 i++;之后为 2,再执行 return i;那不就应该是 2 吗?怎么变成 1 了呢?
关于 Java 虚拟机是如何编译 finally 语句块的问题,有兴趣的读者可以参考《 The JavaTM Virtual Machine Specification, Second Edition 》中 7.13 节 Compiling finally。那里详细介绍了 Java 虚拟机是如何编译 finally 语句块。实际上,Java 虚拟机会把 finally 语句块作为 subroutine(对于这个 subroutine 不知该如何翻译为好,干脆就不翻译了,免得产生歧义和误解。)直接插入到 try 语句块或者 catch 语句块的控制转移语句之前。但是,还有另外一个不可忽视的因素,那就是在执行 subroutine(也就是 finally 语句块)之前,try 或者 catch 语句块会保留其返回值到本地变量表(Local Variable Table)中。待 subroutine 执行完毕之后,再恢复保留的返回值到操作数栈中,然后通过 return 或者 throw 语句将其返回给该方法的调用者(invoker)。请注意,前文中我们曾经提到过 return、throw 和 break、continue 的区别,对于这条规则(保留返回值),只适用于 return 和 throw 语句,不适用于 break 和 continue 语句,因为它们根本就没有返回值。
是不是不太好理解,那我们就用具体的例子来做形象的说明吧!
为了能够解释清单 6 的执行结果,我们来分析一下清单 6 的字节码(byte-code):
Compiled from "Test.java"
public class Test extends java.lang.Object{
public Test();
&& 1:invokespecial#1; //Method java/lang/Object."&init&":()V
& LineNumberTable:
&& line 1: 0
public static void main(java.lang.String[]);
#2; //Field java/lang/System.out:Ljava/io/PrintS
#3; //class java/lang/StringBuilder
invokespecial
#4; //Method java/lang/StringBuilder."&init&":()V
#5; //String return value of getValue():
invokevirtual
&& #6; //Method java/lang/StringBuilder.append:(
&&&&&& Ljava/lang/S)Ljava/lang/StringB
invokestatic
#7; //Method getValue:()I
invokevirtual
&& #8; //Method java/lang/StringBuilder.append:(I)Ljava/lang/StringB
invokevirtual
&& #9; //Method java/lang/StringBuilder.toString:()Ljava/lang/S
invokevirtual
#10; //Method java/io/PrintStream.println:(Ljava/lang/S)V
public static int getValue();
& Exception table:
&& from&& to& target type
&&&& 2&&&& 4&&&& 9&& any
&&&& 9&&& 10&&&& 9&& any
对于 Test()构造方法与 main()方法,在这里,我们不做过多解释。让我们来分析一下 getValue()方法的执行。在这之前,先让我把 getValue()中用到的虚拟机指令解释一下,以便读者能够正确的理解该函数的执行。
1. iconst_
Description: Push the int constant& (-1, 0, 1, 2, 3, 4 or 5) onto the operand stack.
Forms: iconst_m1 = 2 (0x2)& iconst_0 = 3 (0x3)& iconst_1 = 4 (0x4)&
iconst_2 = 5 (0x5) iconst_3 = 6 (0x6)& iconst_4 = 7 (0x7)& iconst_5 = 8 (0x8)
2. istore_
Description: Store int into local variable. The& must be an index into the
local variable array of the current frame.
Forms: istore_0 = 59 (0x3b)& istore_1 = 60 (0x3c)& istore_2 = 61 (0x3d)&
istore_3 = 62 (0x3e)
Description: Load int from local variable. The& must be an index into the
local variable array of the current frame.
Forms: iload_0 = 26 (0x1a)& iload_1 = 27 (0x1b)& iload_2 = 28 (0x1c)& iload_3 = 29 (0x1d)
4. iinc index, const
Description: Increment local variable by constant. The index is an unsigned byte that
must be an index into the local variable array of the current frame. The const is an
immediate signed byte. The local variable at index must contain an int. The value
const is first sign-extended to an int, and then the local variable at index is
incremented by that amount.
Forms:& iinc = 132 (0x84)
Format:
iinc
5. ireturn
Description: Return int from method.
Forms:& ireturn = 172 (0xac)
6. astore_
Description: Store reference into local variable. The& must be an index into the
local variable array of the current frame.
Forms: astore_0 = 75 (0x4b) astore_1 = 76 (0x4c) astore_2 =77 (0x4d) astore_3 =78 (0x4e)
Description: Load reference from local variable. The& must be an index into the
local variable array of the current frame.
Forms: aload_0 = 42 (0x2a) aload_1 = 43 (0x2b) aload_2 = 44 (0x2c) aload_3 = 45 (0x2d)
Description: Throw exception or error.
Forms: athrow = 191 (0xbf)
有了以上的 Java 虚拟机指令,我们来分析一下其执行顺序:分为正常执行(没有 exception)和异常执行(有 exception)两种情况。我们先来看一下正常执行的情况,如图 1 所示:
图 1. getValue()函数正常执行的情况
图 1. getValue()函数正常执行的情况
由上图,我们可以清晰的看出,在 finally 语句块(iinc 0, 1)执行之前,getValue()方法保存了其返回值(1)到本地表量表中 1 的位置,完成这个任务的指令是 istore_1;然后执行 finally 语句块(iinc 0, 1),finally 语句块把位于 0 这个位置的本地变量表中的值加 1,变成 2;待 finally 语句块执行完毕之后,把本地表量表中 1 的位置上值恢复到操作数栈(iload_1),最后执行 ireturn 指令把当前操作数栈中的值(1)返回给其调用者(main)。这就是为什么清单 6 的执行结果是 1,而不是 2 的原因。
再让我们来看看异常执行的情况。是不是有人会问,你的清单 6 中都没有 catch 语句,哪来的异常处理呢?我觉得这是一个好问题,其实,即使没有 catch 语句,Java 编译器编译出的字节码中还是有默认的异常处理的,别忘了,除了需要捕获的异常,还可能有不需捕获的异常(如:RunTimeException 和 Error)。
从 getValue()方法的字节码中,我们可以看到它的异常处理表(exception table), 如下:
Exception table:
from to target type
它的意思是说:如果从 2 到 4 这段指令出现异常,则由从 9 开始的指令来处理。
图 2. getValue()函数异常执行的情况
图 2. getValue()函数异常执行的情况
先说明一点,上图中的 exception 其实应该是 exception 对象的引用,为了方便说明,我直接把它写成 exception 了。
由上图(图 2)可知,当从 2 到 4 这段指令出现异常时,将会产生一个 exception 对象,并且把它压入当前操作数栈的栈顶。接下来是 astore_2 这条指令,它负责把 exception 对象保存到本地变量表中 2 的位置,然后执行 finally 语句块,待 finally 语句块执行完毕后,再由 aload_2 这条指令把预先存储的 exception 对象恢复到操作数栈中,最后由 athrow 指令将其返回给该方法的调用者(main)。
通过以上的分析,大家应该已经清楚 try-catch-finally 语句块的执行流程了吧!
为了更具说服力,我们还是来引经据典吧!大家可以不相信我,难道还不相信“高司令”(Gosling)吗?下面这段仍然摘自 Java 语言规范第四版 《 The Java(TM) Programming Language, Fourth Edition 》 ,请读者自己体会吧!
*******************************************************************************
a finally clause is always entered with a reason. That reason may be that the try code finished normally, that it executed a control flow statement such as return, or that an exception was thrown in code executed in the Try block. The reason is remembered when the finally clause exits by falling out the bottom. However, if the finally block creates its own reason to leave by executing a control flow statement (such as break or return) or by throwing an exception, that reason supersedes the original one, and the original reason is forgotten. For example, consider the following code:
try {
// … do something …
return 1;
} finally {
return 2;
}
When the Try block executes its return, the finally block is entered with the “reason” of returning the value 1. However, inside the finally block the value 2 is returned, so the initial intention is forgotten. In fact, if any of the other code in the try block had thrown an exception, the result would still be to return 2. If the finally block did not return a value but simply fell out the bottom, the “return the value 1 ″ reason would be remembered and carried out.
*******************************************************************************
好了,有了以上的知识,让我们再来看以下 3 个例子。
public class Test {
public static void main(String[] args) {
&&&&&&& System.out
.println("return value of getValue(): " + getValue
());
@SuppressWarnings("finally")
public static int getValue() {
&&&&&&& int i = 1;
&&&&&&& try {
&&&&&&&&&&&&&&&& i = 4;
&&&&&&& } finally {
&&&&&&&&&&&&&&&& i++;
&&&&&&&&&&&&&&&&
清单 7 的执行结果:
return value of getValue(): 5
public class Test {
public static void main(String[] args) {
&&&&&&& System.out
.println("return value of getValue(): " + getValue
());
public static int getValue() {
&&&&&&& int i = 1;
&&&&&&& try {
&&&&&&&&&&&&&&&& i = 4;
&&&&&&& } finally {
&&&&&&&&&&&&&&&& i++;
清单 8 的执行结果:
return value of getValue(): 5
清单 7 和清单 8 应该还比较简单吧!利用我们上面讲解的知识,很容易分析出其结果。让我们再来看一个稍微复杂一点的例子 – 清单 9。我建议大家最好先不要看执行结果,运用学过的知识来分析一下,看是否能推断出正确的结果。
public class Test {
public static void main(String[] args) {&
System.out
.println(test
());&
public static String test() {&
System.out
.println("try block");&
return test1
();&
} finally {&
System.out
.println("finally block");&
public static String test1() {&
System.out
.println("return statement");&
return "after return";&
清单 9 的结果:
return statement
finally block
after return
你分析对了吗?其实这个案例也不算很难,return test1(); 这条语句等同于 :
String tmp = test1();
这样,就应该清楚为什么是上面所示的执行结果了吧!
好了,就写到这吧!希望大家看完这篇文章能够有所收获!
没想到吧!一个小小的、看似简单的 finally 语句块背后居然隐藏了这么多玄机。看来,我们平时还是应该认真的阅读 Java 相关的基础文档,比如:Java 语言规范、Java 虚拟机规范等,很多棘手的问题都可以从中得到答案。只有真正的吃透了基础知识,才能达到运用自如的境界!
浏览: 75508 次
来自: 武汉
不错,讲的挺好的。。。。。
总结很全面}

我要回帖

更多关于 c 传值 传址 的文章

更多推荐

版权声明:文章内容来源于网络,版权归原作者所有,如有侵权请点击这里与我们联系,我们将及时删除。

点击添加站长微信