Java堆栈
jvm为每个新创建的线程都分配一个堆栈。堆栈以帧为单位保存
线程的状态。jvm对堆栈只进行两种操作:以帧为单位的压栈和出栈
操作。
某个线程正在执行的方法称为此线程的当前方法。当前方法使用的帧称
为当前帧。当前方法所属的类称为当前类。当前类的常量池称为当前
常量池。当线程执行一个方法时,它会跟踪当前的类和常量池。当jvm
会在当前帧内执行帧内数据的操作。
当线程激活一个java方法,jvm就会在线程的java堆栈里新压入一个帧。
这个帧自然成为了当前帧。在此方法执行期间,这个帧将用来保存参数,
局部变量,中间计算过程和其他数据。
一个方法可以以两种方法结束。一种是正常返回结束。一种是通过
异常抛出而异常结束(abrupt completion)。不管以那种方式返回,jvm
都会将当前帧弹出堆栈然后释放掉,这样上一个方法的帧就成为当前帧了。
(译者:可能可以这样理解,位于堆栈顶部的帧为当前帧)
java堆栈上的所有数据都为此线程私有。一个线程不能访问另一个线程
的堆栈数据,所以在多线程的情况下也不需要对堆栈数据的访问进行同步。
象方法区和堆一样(见以前的译文),java堆栈和帧在内存中也不必是连续
的。帧可以分布在连续的内存区,也可以不是。帧的数据结构由jvm的实现者
来决定,他们可以允许用户指定java堆栈的初始大小或最大最小尺寸。
堆栈帧( The Stack Frame)
堆栈帧有三部分:局部变量区,操作数堆栈和帧数据区。局部变量区和操作数堆栈
的大小要视对应的方法而定。编译器在编译的时候就对每个方法进行了计算并放在
了类文件(class file)中了。帧数据区的大小对一种jvm实现来说是一定的。
当jvm激活一个方法时,它从类信息数据得到此方法的局部变量区和操作数堆栈的
大小,并据此分配大小合适堆栈帧压入java堆栈中。
局部变量区
java堆栈帧的局部变量区是一个基为零类型为word的数组。指令通过索引来
使用这些数据。类型为int,float,reference和returnAddress的值在
数组中占据一项,类型为byte,short,和char的值在存入数组前都转为了
int值而占据一项。类型为long和double的值在数组中占据连续的两项,在
访问他们的时候,指令提供第一项的索引。例如一个long值占据3,4项,指令会
取索引为3的long值。局部变量区的所有值都是字对齐的,long和doubles
的起始索引值没有限定。
局部变量区包含此方法的参数和局部变量。编译器首先以声明的顺序把参数
放入局部数据区。图5-9显示了下面两个方法的变量区。
// On CD-ROM in file jvm/ex3/Example3a.java
class Example3a {
public static int runClassMethod(int i, long l, float f,
double d, Object o, byte b) {
return 0;
}
public int runInstanceMethod(char c, double d, short s,
boolean b) {
return 0;
}
}
图5-9. 局部变量区中的方法参数
注意在方法runInstanceMethod()的帧中,第一个参数是一个
类型为reference的值,尽管方法没有显示的声明这个参数,但
这是个对每个实例方法(instance method)都隐含加入的一个
参数值,用来代表调用的对象。(译者:与c++中的this指针一样)
我们看方法runClassMethod()就没有这个变量,这是因为这是一
个类方法(class method),类方法与类相关,而不与对象相关。
我们注意到在源码中的byte,short,char和boolean在局部变量区
都成了ints。在操作数堆栈也是同样的情况。如前所述,jvm不直接
支持boolean类型,java编译器总是用ints来表示boolean。但java
对byte,short和char是支持的,这些类型的值可以作为实例变量
存储在局部变量区中,也可以作为类变量存储在方法区中。但在局部变量区
和操作数堆栈中都被转成了ints类型的值,期间的运算也是以int来的,
只当存回堆或方法区中,才会转回原来的类型。
同样需要注意的是runClassMethod()的对象o。在java中,所以的对象
都以引用(reference)传递。所有的对象都存储在堆中,你永远都不会在
局部变量区或操作数堆栈中发现对象的拷贝,只会有对象引用。
编译器对局部变量的放置方法可以多种多样,它可以任意决定放置顺序,
甚至可以用一个索引指代两个局部变量。例如,当两个局部变量的作用域
不重叠时,如Example3b的局部变量i和j。
// On CD-ROM in file jvm/ex3/Example3b.java
class Example3b {
public static void runtwoLoops() {
for (int i = 0; i < 10; ++i) {
System.out.println(i);
}
for (int j = 9; j >= 0; --j) {
System.out.println(j);
}
}
}
jvm的实现者对局部变量区的设计仍然有象其他数据区一样的灵活性。
关于long和double数据如何分布在数组中,jvm规范没有指定。
假如一个jvm实现的字长为64位,可以把long或double数据放在
数组中的低项内,而使高项为空。(在字长为32位的时候,需要两项
才能放下一个long或double)。
操作数堆栈
操作数堆栈象局部变量区一样是用一个类型为word的数组存储数据,
但它不是通过索引来访问的,而是以堆栈的方式压入和弹出。假如
一个指令压入了一个值,另一个指令就可以弹出这个值并使用之。
jvm在操作数堆栈中的处理数据类型的方式和局部变量区是一样的,同样
有数据类型的转换。jvm没有寄存器,jvm是基于堆栈的而不是基于寄存器
的,因为jvm的指令从堆栈中获得操作数,而不是寄存器。虽然操作数还可以
从另外一些地方获得,如字节码中,或常量池内,但主要是从堆栈获得的。
jvm把操作数堆栈当作一个工作区使用。许多指令从此堆栈中弹出数据,进行
运算,然后压入结果。例如,iadd指令从堆栈中弹出两个数,相加,然后压入
结果。下面显示了jvm是如何进行这项操作的:
iload_0 // push the int in local variable 0
iload_1 // push the int in local variable 1
iadd // pop two ints, add them, push result
istore_2 // pop int, store into local variable 2
在这个字节码的序列里,前两个指令iload_0和iload_1将存储在
局部变量区中索引为0和1的整数压入操作数据区中,然后相加,将
结果压入操作数据区中。第四条指令istore_2从操作数据区中弹出
结果并存储到局部数据区索引为2的地方。在图5-10中,详细的表述
了这个过程,图中,没有使用的区域以空白表示。
图5-10. 两个局部变量的相加.
帧数据区
除了局部变量区和操作数据堆栈外,java栈帧还需要数据来支持
常量池解析(constant pool resolution),方法的正常返回
(normal method return)和异常分派(exception dispatch)。
这些信息保存在帧数据区中。
jvm中的许多指令都涉及到常量池的数据。一些指令仅仅是取出常量池
中的数据并压入操作数堆栈中。一些指令使用常量池中的数据来指示
需要实例化的类或数组,需要访问的域,或需要激活的方法。还有一些
指令来判断某个对象是否是常量池指定的某个类或接口的子孙实例。
每当jvm要执行需要常量区数据的指令,它都会通过帧数据区中指向
常量区的指针来访问常量区。以前讲过,常量区中对类型,域和方法
的引用在开始时都是符号。如果当指令执行的时候仍然是符号,jvm
就会进行解析。
除了常量区解析外,帧数据区还要帮助jvm处理方法的正常和异常结束。
正常结束,jvm必须恢复方法调用者的环境,包括恢复pc指针。假如
方法有返回值,jvm必须将值压入调用者的操作数堆栈。
为了处理方法的异常退出,帧数据区必须保存对此方法异常表的引用。
一个异常表定义了这个方法受catch子句保护的区域,每项都有一个
catch子句的起始和开始位置(position),和用来表示异常类在常量池
中的索引,以及catch子句代码的起始位置。
当一个方法抛出异常时,jvm使用帧数组区指定的异常表来决定如何处理。
如果找到了匹配的catch子句,就会转交控制权。如果没有发现,方法会
立即结束。jvm使用帧数据区的信息恢复调用者的帧,然后重新抛出同样
的异常。
除了上述信息外,jvm的实现者也可以将其他信息放入帧数据区,如调试
数据。
java堆栈的一种实现
实现者可以按自己的想法设计java堆栈。如以前所讲,一个方法是从堆中
单独的分配帧。我以此为例,看下面的类:
// On CD-ROM in file jvm/ex3/Example3c.java
class Example3c {
public static void addAndPrint() {
double result = addTwoTypes(1, 88.88);
System.out.println(result);
}
public static double addTwoTypes(int i, double d) {
return i + d;
}
}
图5-11显示了一个线程执行这个方法的三个快照。在这个jvm的实现中,
每个帧都单独的从堆中分配。为了激活方法addTwoTypes(),方法
addAndPrint()首先压入int 1和double88.88到操作数堆栈中,然后
激活addTwoTypes()方法。
图5-11. 帧的分配
激活addTwoTypes()的指令使用了常量池的数据,jvm在常量池中查找这些数据
如果有必要则解析之。
注意addAndPrint()方法使用常量池引用方法addTwoTypes(),尽管
这两个方法是属于一个类的。象引用其他类一样,对同一个类的方法和域
的引用在初始的时候也是符号,在使用之前需要解析。
解析后的常量池数据项将指向存储在方法区中有关方法addTwoTypes()的信息。
jvm将使用这些信息决定方法addTwoTypes()局部变量区和操作数堆栈的大小。
如果使用Sun的javac编译器(JDK1.1)的话,方法addTwoTypes()的局部变量区
需要三个words,操作数堆栈需要四个words。(帧数据区的大小对某个jvm实现
来说是定的)jvm为这个方法分配了足够大小的一个堆栈帧。然后从方法
addAndPrint()的操作数堆栈中弹出double参数和int参数(88.88和 1)并把他们
分别放在了方法addTwoType()的局部变量区索引为1和0的地方。
当addTwoTypes()返回时,它首先把类型为double的返回值(这里是89.88)
压入自己的操作数堆栈里。jvm使用帧数据区中的信息找到调用者(为
addAndPrint())的堆栈帧,然后将返回值压入addAndPrint()的操作数堆栈
中并释放方法addTwoType()的堆栈帧。然后jvm使addTwoType()的堆栈帧
为当前帧并继续执行方法addAndPrint()。
图5-12显示了相同的方法在不同的jvm实现里的执行情况。这里的堆栈帧是在
一个连续的空间里的。这种方法允许相邻方法的堆栈帧可以重叠。这里调用者的
操作数堆栈就成了被调者的局部变量区。
图5-12. 从一个连续的堆栈中分配帧
这种方法不仅节省了空间,而且节省了时间,因为jvm不必把参数从一个
堆栈帧拷贝到另一个堆栈帧中了。
注意当前帧的操作数堆栈总是在java堆栈的顶部。尽管这样可能
可以更好的说明图5-12的实现。但不管java堆栈是如何实现的,
对操作数堆栈的操作总是在当前帧执行的。这样,在当前帧的
操作数堆栈压入一个数也就是在java堆栈压入一个值。
java堆栈还有一些其他的实现,基本上是上述两种的结合。一个jvm可以
在线程初期时从堆栈分出一段空间。在这段连续的空间里,jvm可以采用
5-12的重叠方法。但在与其他段空间的结合上,就要使用如图5-11的方法。
分享到:
相关推荐
Thread Dump 是非常有用 Java应用问题的工具。每一个 Java 虚拟机 都有及时生成所有线程在某...照,及JVM 中所有 Java线程的堆栈跟踪信息,堆栈信息一般包含完整的类名 及所执行的方法,如果可能的话还有源代码的行数。
IBM提供了一款名为HeadAnalyzer的工具,版本4.1.4,专门用于分析Java堆栈信息,尤其适用于WebSphere应用服务器环境。HeadAnalyzer能够帮助开发者和系统管理员深入理解JVM内部的工作机制,识别内存泄漏、线程阻塞以及...
Java堆栈是一个重要的内存区域,它是Java虚拟机(JVM)的一部分,主要负责管理方法的执行。在这个"java 堆栈的演示程序"中,我们可能会深入理解堆栈的工作原理以及它在运行jsp程序时的角色。源代码设计将帮助我们...
Java堆栈是Java虚拟机(JVM)的一部分,主要用于存储方法调用的局部变量、操作数栈、动态链接信息和返回地址等。当一个方法被调用时,一个新的栈帧会被创建并压入堆栈;而当方法执行完毕,相应的栈帧就会被弹出堆栈...
Java堆栈分析是Java应用程序性能调优的重要环节,特别是在服务器环境中,当CPU资源占用过高或者出现内存泄露等问题时,分析Java堆栈能够帮助我们找出问题的根源。在本例中,我们将通过一系列步骤来理解如何进行Java...
本文将深入探讨Java堆栈的概念、工作原理以及它们在程序执行中的角色。 1. 堆(Heap) - 堆是Java内存模型中的主要部分,主要用于存储对象实例。所有的类实例和数组都在堆中分配内存。 - 堆内存是动态分配的,...
《JVM堆栈性能分析》PDF是一份深入探讨Java虚拟机(JVM)内存管理和性能优化的专业文档。本文档主要关注JVM中的堆栈部分,尤其是如何理解和优化其性能,对于Java开发者来说具有很高的学习价值。JVM是Java程序运行的...
Java 堆栈是 Java 中的一种基本数据结构,它是 JVM(Java Virtual Machine)为每个新创建的线程分配的。堆栈以帧为单位保存线程的状态,JVM 对堆栈只进行两种操作:以帧为单位的压栈和出栈操作。 Java 堆栈的结构...
Java堆栈内存分析是Java编程中的重要概念,它关乎程序的性能优化和内存泄漏的预防。堆和栈是Java内存管理的两个主要区域,它们各自承担着不同的职责。本笔记将深入探讨这两个区域的工作原理以及如何进行有效的分析。...
在我看来线程堆栈分析技术是Java EE产品支持工程师所必须掌握的一门技术。在线程堆栈中存储的信息,通常远超出你的想象,我们可以在工作中善加利用这些信息。 我的目标是分享我过去十几年来在线程分析中积累的...
在IT行业中,尤其是在Java应用程序的开发和调试过程中,理解并分析线程堆栈是非常关键的。线程堆栈反映了程序运行时的线程状态,帮助开发者定位和解决多线程问题,如死锁、资源竞争等。IBM为WebSphere Application ...
此外,`jstack`工具是Java开发工具集(JDK)的一部分,它能以命令行形式输出JVM中线程的详细堆栈跟踪信息,这对于远程服务器上的应用非常有用。在命令行中运行`jstack <pid>`(pid是Java进程的ID),即可查看该进程...
标题中提到了JVM原理、JVM调优、JVM内存模型和JAVA并发,这些都是Java虚拟机(JVM)相关的核心概念。JVM是运行Java字节码的虚拟计算机,为Java提供了一个跨平台的环境,确保Java程序可以在不同的操作系统上运行而...
尽管不同JVM打印的Thread Dump可能略有差异,但它们通常提供了当前所有活动线程的状态信息以及JVM中所有Java线程的堆栈跟踪详情。这些堆栈信息包括完整的类名、执行的方法以及可能的源代码行号。 **1.2 Thread Dump...
JVM拥有自己的硬件架构,包括处理器、堆栈、寄存器等,并具备一套完整的指令系统。 #### 三、Java虚拟机基础架构 ##### 3.1 什么是虚拟机? 虚拟机(Virtual Machine, VM)是一种能够模拟特定计算机体系结构、执行...
如何调优JVM - 优化Java 虚拟机(大全+ 实例) 堆设置 -Xmx3550m :设置JVM 最大堆内存为3550M 。 -Xms3550m :设置JVM 初始堆内存为3550M。此值可以设置与-Xmx 相同,以避免每次垃 圾回收完成后JVM 重新分配内存。 ...
Java JVM 详解 Java JVM 是 Java 语言的核心组件之一,负责将 Java 字节码翻译成机器语言并执行。要深入了解 JVM,可以从 Java 的特性入手,描绘 JVM 的大致应用,然后细细阐述 JVM 的原理及内存管理机制和调优。...
不同于 C/C++ 这类需要针对不同平台进行编译的语言,Java 采用了一种更为灵活的方式:将 Java 源代码编译为字节码(Bytecode),这种字节码可以在任何安装了 JVM(Java 虚拟机)的平台上运行。这种方式确保了 Java ...
此外,JVM还提供了丰富的命令行工具,如`jinfo`用于查看或修改JVM配置,`jstat`用于收集JVM的各种统计信息,`jmap`用于生成堆转储文件以供分析,以及`jstack`用于打印线程堆栈跟踪,这些都是诊断和优化Java应用程序...