天天看点

JVM学习笔记-栈(Stack)

与程序计数器一样,Java虚拟机栈(Java Virtual Machine Stacks)也是线程私有的,它的生命周期与线程相同。虚拟机栈描述的是Java方法执行的内存模型:每个方法被执行的时候都会同时创建一个栈帧(Stack Frame①)用于存储局部变量表、操作栈、动态链接、方法出口等信息。每一个方法被调用直至执行完成的过程,就对应着一个栈帧在虚拟机栈中从入栈到出栈的过程。

经常有人把Java内存区分为堆内存(Heap)和栈内存(Stack),这种分法比较粗糙,Java内存区域的划分实际上远比这复杂。这种划分方式的流行只能说明大多数程序员最关注的、与对象内存分配关系最密切的内存区域是这两块。其中所指的“堆”在后面会专门讲述,而所指的“栈”就是现在讲的虚拟机栈,或者说是虚拟机栈中的局部变量表部分。 

       局部变量表存放了编译期可知的各种基本数据类型(boolean、byte、char、short、int、float、long、double)、对象引用(reference类型,它不等同于对象本身,根据不同的虚拟机实现,它可能是一个指向对象起始地址的引用指针,也可能指向一个代表对象的句柄或者其他与此对象相关的位置)和returnAddress类型(指向了一条字节码指令的地址)。 

      其中64位长度的long和double类型的数据会占用2个局部变量空间(Slot),其余的数据类型只占用1个。局部变量表所需的内存空间在编译期间完成分配,当进入一个方法时,这个方法需要在帧中分配多大的局部变量空间是完全确定的,在方法运行期间不会改变局部变量表的大小。 

在Java虚拟机规范中,对这个区域规定了两种异常状况:如果线程请求的栈深度大于虚拟机所允许的深度,将抛出StackOverflowError异常;如果虚拟机栈可以动态扩展(当前大部分的Java虚拟机都可动态扩展,只不过Java虚拟机规范中也允许固定长度的虚拟机栈),当扩展时无法申请到足够的内存时会抛出OutOfMemoryError异常。 

When a new thread is launched, the Java Virtual Machine creates a new Java stack for the thread. As mentioned earlier, a Java stack stores a threadís state in discrete frames. The Java Virtual Machine only performs two operations directly on Java Stacks: it pushes and pops frames.

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

The method that is currently being executed by a thread is the thread’s current method. The stack frame for the current method is the current frame. The class in which the current method is defined is called the current class, and the current classís constant pool is the current constant pool. As it executes a method, the Java Virtual Machine keeps track of the current class and current constant pool. When the virtual machine encounters instructions that operate on data stored in the stack frame, it performs those operations on the current frame.

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

When a thread invokes a Java method, the virtual machine creates and pushes a new frame onto the threadís Java stack. This new frame then becomes the current frame. As the method executes, it uses the frame to store parameters, local variables, intermediate computations, and other data.

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

A method can complete in either of two ways. If a method completes by returning, it is said to have normal completion. If it completes by throwing an exception, it is said to have abrupt completion. When a method completes, whether normally or abruptly, the Java Virtual Machine pops and discards the method’s stack frame. The frame for the previous method then becomes the current frame.

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

All the data on a threadís Java stack is private to that thread. There is no way for a thread to access or alter the Java stack of another thread. Because of this, you need never worry about synchronizing multi-threaded access to local variables in your Java programs. When a thread invokes a method, the methodís local variables are stored in a frame on the invoking threadís Java stack. Only one thread can ever access those local variables: the thread that invoked the method.

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

Like the method area and heap, the Java stack and stack frames need not be contiguous in memory. Frames could be allocated on a contiguous stack, or they could be allocated on a heap, or some combination of both. The actual data structures used to represent the Java stack and stack frames is a decision of implementation designers. Implementations may allow users or programmers to specify an initial size for Java stacks, as well as a maximum or minimum size.

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