自己动手写写:关于jvm的理解(3)

上篇文章讲到了Heap(堆)相关的内容,下面紧接着讲讲Runtime Data Areas(运行数据区)中Java Stacks、PC Register、Native Method Stacks.

 

Java Stacks

 

      每当启动一个新线程时,Java虚拟机都会为它分配一个Java栈。前面我们曾经提到,Java栈出帧为单位保存线程的运行状态。虚拟机只会直接对Java栈执行两种操作:以帧为单位的压栈或出栈。

      某个线程正在执行的方法被称为该线程的当前方法,当前方法使用的栈帧称为当前帧,当前方法所属的类称为当前类,当前类的常量池称为当前常量池。在线程执行一个方法时,他会跟踪当前类和当前常量池。此外当虚拟机遇到栈内操作指令时,他对当前帧内数据执行操作。

      每当线程调用一个Java方法时,虚拟机都会在该线程的Java栈中压入一个新帧。而这个新帧自然就成为了当前帧。在执行这个方法时,他使用这个帧来存数参数、局部变量、中间运算结果等等数据。

      Java方法可以以两种方式完成。一种通过return返回的,称为正常返回;一种是通过抛出异常而异常终止的。不管哪种方式返回,虚拟机都会将当前帧弹出Java栈然后释放掉,这样上一个方法的真就成为当前帧了。

      Java栈上的所有数据都是此线程私有的。任何线程都不能访问另外一个线程的栈数据,因此我们不需要考虑多线程情况下栈数据的访问同步问题。当一个线程调用一个方法时,方法的局部变量保存在调用线程Java栈的帧中。只有一个线程能总是访问那些局部变量,即调用方法的线程。

      像方法区和堆一样,Java栈和帧在内存中也不是连续的。帧可以分布在连续的栈里,也可以分步在堆里,或者二者兼而有之。表示Java栈和栈帧的实际数据结构由虚拟机的实现者决定,某些实现允许用户指定Java栈的初始大小和最大最小值。

 

栈帧由三部分组成:局部变量区操作数栈帧数据区(保存一些数据来支持常量池解析、正常方法返回以及异常派发机制)


上图摘自http://rednaxelafx.iteye.com/blog/656951

 

PC Register

 

       对于一个运行中的Java程序而言,其中的每一个线程都有他自己的PC(程序计数器)寄存器它是在该线程启动时创建的。PC寄存器的大小是一个字长,因此他既能够持有一个本地指针,也能够持有一个returnAddress。当线程执行某个Java方法时,PC寄存器的内容总是下一条将被执行指令的"地址",这里的“地址”可以是一个本地指针,也可以是在方法字节码中相对于该方法起始指令的偏移量。如果该线程正在执行一个本地方法,那么此时PC寄存器的值是“undefined”。

 

 

Native Method Stacks

 

        前面提到的所有运行时数据区都是Java虚拟机规范中明确定义的,除此之外,对已一个运行中的Java程序而言,他还可能会用到一些本地方法相关的数据区。当某个线程调用一个本地方法时,他就进入了一个全新的并且不再受虚拟机限制的世界,本地方法可以通过本地方法接口来访问虚拟机得运行时数据区,但不止于此,他还可以做任何他想做的事情。比如,他甚至可以直接使用本地处理器中的寄存器,或者直接从本地内存的堆中分配任意数量的内存等等。宗旨,他和虚拟机拥有同样的权限(或者说能力)。,

       本地方法本质上是依赖于实现的,虚拟机实现的设计者可以自由地决定使用怎样的机制来让Java程序调用本地方法。

       任何本地方法接口都会使用某种本地方法栈。当线程调用Java方法时,虚拟机会创建一个新的栈帧并压入java栈。然而当他调用的是本地方法时,虚拟机会保持Java栈不变,不再在线程的java栈中压入新的帧,虚拟机只是简单地动态连接并直接调用指定的本地方法。可以把这看做是虚拟机利用本地方法来动态扩展自己。就如同Java虚拟机的实现在按照其中运行的Java程序的吩咐,调用属于虚拟机内部的另一个(动态连接的)方法。

        如果某个虚拟机实现的本地方法接口是使用C连接模型的话,那个他的本地方法栈就是C栈。我们知道,当C程序调用一个C函数时,其栈操作都是确定的。传递给该函数的参数已某个确定的顺序压入栈,他的返回值也以确定的方式传回调用者。同样,这就是改虚拟机实现中本地方法栈的行为。

        很可能本地方法接口需要回调Java虚拟机中的Java方法(这也是由设计者决定的),在这种情形下,该线程会保存本地方法栈的状态并进入到另一个Java栈。

 

 

         下图描绘了这种情况,就是当一个线程调用一个本地方法时,本地方法又回调虚拟机中的另一个Java方法。这幅图展示了java虚拟机内部线程运行的全景图。一个线程可能在整个生命周期中都执行Java方法,操作他的Java栈;或者他可能毫无障碍地在Java栈和本地方法栈之间跳转。

         上图所示,改线程首先调用了两个Java方法,而第二个Java方法又调用了一个本地方法,这样导致虚拟机使用了一个本地方法栈。图中的本地方法栈显示为一个连续的内存空间。假设这是一个C语言栈,期间有两个C函数,他们都以包围在虚线中的灰色块表示。第一个C函数被第二个Java方法当做本地方法调用,而这个C函数又调用了第二个C函数。之后第二个C函数被第二个Java方法当做本地方法调用,而这个C函数又调用了第二个C函数。之后第二个C函数又通过本地方法接口回调了一个Java方法(第三个Java方法)。最终这个Java方法又调用了一个Java方法(他成为图中的当前方法)。

         就像其他运行时内存区一样,本地方法栈占用的内存区也不必是固定大小的,他可以根据需要动态扩展或者收缩。某些是实现也允许用户或者程序员指定该内存区的厨师大小以及最大最小值。

 

 

         至此运行数据区的内容就已经介绍完了。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值