Java栈

我们天天在说java堆java栈,究竟什么是java栈?

首先需要明确一个概念,那就是通常我们说的堆、栈与java堆、java栈并不是一个东西。java堆和java栈可能都是在我们通常所说的堆中划分出内存的。在java层面上,将这划分出的内存按其用途称为java堆、java栈。

《The Java Virtual Machine Specification Java SE 7 Edition》(中文版《Java 虚拟机规范(Java SE 7 版)》)中对java栈是这么解释的:

每一个Java虚拟机线程都有自己私有的Java虚拟机栈(Java Virtual Machine Stack),这个栈与线程同时创建,用于存储栈帧(Frames, §2.6)。Java虚拟机栈的作用与传统语言(例如 C语言)中的栈非常类似,就是用于存储局部变量与一些过程结果的地方。另外,它在方法调用和返回中也扮演了很重要的角色。因为除了栈帧的出栈和入栈之外,Java虚拟机栈不会再受其他因素的影响,所以栈帧可以在堆(此堆不是java堆,就是我们通常意义所说的堆)中分配
,Java虚拟机栈所使用的内存不需要保证是连续的。

Java虚拟机规范允许 Java虚拟机栈被实现成固定大小的或者是根据计算动态扩展和收缩的。如果采用固定大小的 Java虚拟机栈设计,那每一条线程的Java虚拟机栈容量应当在线程创建的时候独立地选定。Java虚拟机实现应当提供给程序员或者最终用户调节虚拟机栈初始容量的手段,对于可以动态扩展和收缩 Java虚拟机栈来说,则应当提供调节其最大、最小容量的手段。

接下来对此一一解读。
1、当创建了一个新线程时,都会为其分配一个java栈,另外还会分配一个程序计数器。
2、Java栈里能pop或push的内容是栈帧(Stack Frame),用类java伪码表示是这样的Stack<StackFrame>.当线程调用一个方法时,将一个新栈帧push进java栈,当一个方法调用结束(无论是正常结束还是抛出异常),从java栈中pop出一个栈帧。
3、栈帧(Stack Frame)的内容分为三部分:局部变量区、操作数栈、帧数据区。用java伪码可大概表示如下:
class StackFrame {
LocalVariable lv;
OperationStack os;
FrameData fd;
……
}

4、栈帧的作用:执行一个方法时,用新入栈的栈帧存储参数、局部变量、中间运算结果等。
5、因为每个线程都有自己的java栈,所以java栈中的所有数据都是该线程私有的,不存在并发安全问题。
6、java栈和栈帧在内存中不必是连续的,栈帧可以在堆(非java堆)中分配,java栈和栈帧的具体结构由jvm实现者自行决定。java栈的大小可以是固定的也可是动态计算的大小。

前面提到java栈里面存的是栈帧,栈帧包含三部分,接下来解释下这三部分的作用。

1、局部变量区
局部变量区,顾名思义,是用来存在局部变量和方法调用的参数的。局部变量区类似于数组,是用索引来访问的。如果是非静态方法,局部变量区索引为0的位置是当前对象this的引用。假如有以下方法:

public

void

test(String name) {

    int

a = ...;

    ...
...

}

局部变量区存储的内存非配可能是这样的:局部变量区索引为0的位置是this引用,索引为1的位置是参数name,索引为2的位置是int变量a…
如果是静态方法,索引为0的位置就不是this引用,需要所有的位置往前推一个。
需要注意的是,局部变量区中可以存储基本类型(int,double,long,float,returnAddress; boolean,byte,short,char,在这里都转换已经隐式的转换成了int)和引用类型(reference)。如果参数是对象类型,如上面的String,局部变量区绝对不会出现java对象(如某个String值),只会是指向该对象的引用(直接或间接的)。若是基本类型,则存储的是其值。同时,long和double占用了,连续的两个索引位置(即使一个索引位置能装得下long或double值,也会用两个索引位置来存储)。
我们可以做些推断,在方法调用的时候其java栈帧就要入栈,也就是说,调用方法前就已经知道调用这个方法需要多大的局部变量区了(同样操作数栈的大小也是确定下来了的),事实确实如此,在编译的时候这些信息就已经确定下来了。换个思维,在调用java方法的时候,如果声明了局部变量,它是在调用前就已经为其分配好内存了,而不是调用过程中。那么,在for循环中声明变量会是怎么处理呢?如下面的代码:

public

class

Test {

    public

static

void

main(String[] args) {

        for(int

i=
0;
i<
1000;
i++) {

            String
s = String.valueOf(i);

            System.out.println(s);

        }

    }

}

基于上面的理论,可以肯定的是,它不会在循环过程中不停的为s分配内存。那它会在调用main方法新建的栈帧的局部变量区中分配1000多个位置吗?如果这件事让我们自己做,我们会怎么处理呢?s在每次迭代开始之前就出了作用域,那么我就可以重用局部变量区的s嘛,真实情况如何?我们运行下javap -c Test,得到main方法如下:

public

static

void

main(java.lang.String[]);

  Code:

   0:  
iconst_0

   1:  
istore_1

   2:  
iload_1

   3:  
sipush 
1000

   6:  
if_icmpge      
27

   9:  
iload_1

   10
invokestatic    #
2;//Method
java/lang/String.valueOf:(I)Ljava/lang/String;

   13
astore_2

   14
getstatic       #
3;//Field
java/lang/System.out:Ljava/io/PrintStream;

   17
aload_2

   18
invokevirtual   #
4;//Method
java/io/PrintStream.println:(Ljava/lang/String;)V

   21
iinc   
1,
1

   24
goto   

2

   27
return

}

在第10条指令调用完String.valueOf(i)后,第13条将其存入局部变量区索引为2的位置,也就是s,OK,我们看到了,它确实一直重用局部变量区索引为2的位置来存取每次迭代中声明的s。

2、操作数栈
操作数栈的作用主要用来存储运算结果以及运算的操作数。如果局部变量区一样,它可以存储基本类型(int,double,long,float,returnAddress; boolean,byte,short,char,在这里都转换已经隐式的转换成了int)和引用类型(reference),对象是以引用的形式出现在操作数栈里,操作数栈里也永远不可能出现对象,只会是对象的引用。操作数栈的操作方式不同于局部变量区,操作数栈是以入栈和出栈的形式操作的,如上面main方法去掉System.out.println(s)后的指令集合的含义如下(假设【】表示栈,左边为栈顶):

public

static

void

main(java.lang.String[]);

  Code:

   0:  
iconst_0
//将0入栈,栈内容【0】

   1:  
istore_1
//从栈中弹出一个int,存储到局部变量区索引为1的位置,栈内容【】

   2:  
iload_1
//将局部变量区索引为1的int值入栈,栈内容:【0】

   3:  
sipush 
1000//将1000入栈,栈内容【1000,0】

   6:  
if_icmpge 
27//从栈中弹出两个int,比较器大小,若大于或等于,跳到指令27,栈内容【】

   9:  
iload_1

   //将局部变量区索引为1的int值入栈,栈内容:【num】(循环中第21条指令不停的自增局部变量区索引为1的值,该值是动态变化的)

   10
invokestatic

   //从栈顶弹出一个int,作为参数调用String.valueOf方法,并将调用结果入栈。栈内容【reference】(String.valueOf的返回值是一个String对象,栈里存的是指向该对象的引用)

   13
astore_2   
//将栈顶元素出栈,存到局部变量区索引为2的位置,栈内容【】

   21
iinc   
1,
1//将局部变量区索引为1的int值加1,栈内容【】

   24
goto   

2

//跳回指令2,栈内容【】

   27
return//方法调用结束

}

3、帧数据区
帧数据区用来支持常量池解析,方法正常返回,方法异常处理等内容。

—————–
更多阅读:《深入java虚拟机 第二版》第五章,《The Java Virtual Machine Specification Java SE 7 Edition》§2.5,§2.6

时间: 2024-12-10 05:27:51

Java栈的相关文章

Java栈与堆一篇好文

Java栈与堆   ----对这两个概念的不明好久,终于找到一篇好文,拿来共享   1. 栈(stack)与堆(heap)都是Java用来在Ram中存放数据的地方.与C++不同,Java自动管理栈和堆,程序员不能直接地设置栈或堆.   2. 栈的优势是,存取速度比堆要快,仅次于直接位于CPU中的寄存器.但缺点是,存在栈中的数据大小与生存期必须是确定的,缺乏灵活性.另外,栈数据可以共享,详见第3点.堆的优势是可以动态地分配内存大小,生存期也不必事先告诉编译器,Java的垃圾收集器会自动收走这些不再

java-关于Java栈的问题,它是共享的吗

问题描述 关于Java栈的问题,它是共享的吗 我在Java虚拟机那本书里面看到说虚拟机栈是线程私有的,但是为什么网上一大片都说栈可以共享数据?线程私有怎么共享?我的理解是,对于每一个线程,它的栈里面的值是相同的?所以说栈可以共享数据也没什么问题了? 解决方案 栈是线程私有的,意思是说每个线程都有自己的栈. 而栈可以共享数据是说,线程a在栈上的数据,线程b也是可以访问的(比如a把某个栈上变量传给了b),因为他们在一个进程内,数据访问是没有问题的. 对于每一个线程,栈里面的内容当然不一样,你要去找文

java虚拟机 jvm 出入java栈 栈空间内存分配

java栈空间是一块线程私有的内存空间,java堆和程序数据密切相关,那么java栈就是和线程执行密切相关.线程最基本的执行行为就是函数的调用.每次函数调用其实是通过java栈传递数据的. 数据结构中的栈的特性:先进后出,后进先出.FIFO. java内存中的栈跟数据结构中的特性相似也是FIFO.但是只支持进栈和出栈操作. java栈中保存的主要内容是栈帧.每一次函数调用都会有对应的栈帧被压进去java栈,执行完毕的时候被弹出java栈.如下图所示. 函数1对应栈帧1,函数2对应栈帧2.函数3对

关于Java栈与堆的思考

1.栈(stack)与堆(heap)都是Java用来在Ram中存放数据的地方.与C++不同,Java自动管理栈和堆,程序员不能直接地设置栈或堆. 2.栈的优势是,存取速度比堆要快,仅次于直接位于CPU中的寄存器.但缺点是,存在栈中的数据大小与生存期必须是确定的,缺乏灵活性.另外,栈数据可以共享,详见第3点.堆的优势是可以动态地分配内存大小,生存期也不必事先告诉编译器,Java的垃圾收集器会自动收走这些不再使用的数据.但缺点是,由于要在运行时动态分配内存,存取速度较慢. 3.Java中的数据类型有

Java栈的实例-数组和链表两种方法(转)

一.栈 栈的定义 栈(Stack)是限制仅在表的一端进行插入和删除运算的线性表. (1)通常称插入.删除的这一端为栈顶 (Top),另一端称为栈底 (Bottom). (2)当表中没有元素时称为空栈. (3)栈为后进先出(Last In First Out)的线性表,简称为 LIFO 表. 栈的修改是按后进先出的原则进行.每次删除(退栈)的总是当前栈中" 最新"的元素,即最后插入(进栈)的元素,而最先插入的是被放在栈的底部, 要到最后才能删除.  2.栈的基本运算  (1) 判断栈是否

java栈中变量的作用域与内存回收问题上的一点小疑问?

问题描述 一个类的实例方法public void test(){ //dosomethings ... for(int i=0;i<100;i++){ //dosomethings ... } //dosomethings.....}这段方法在执行时,变量i的作用域在for循环块中,当出了for循环块以外,变量i失效.但是我想问一下,当出了for循环块以后,变量i的所在内存是否会被回收.也就是说是否在此test方法的栈帧出栈之前被回收? 解决方案 当调用一个java方法时,产生一个帧,帧里面包括

关于Java栈的问题

问题描述 我在Java虚拟机那本书里面看到说虚拟机栈是线程私有的,但是为什么网上一大片都说栈可以共享数据?线程私有怎么共享?我的理解是,对于每一个线程,它的栈里面的值是相同的?所以说栈可以共享数据也没什么问题了? 解决方案

java内存管理(堆、栈、方法区)

java内存管理 简介 首先我们要了解我们为什么要学习java虚拟机的内存管理,不是java的gc垃圾回收机制都帮我们释放了内存了吗?但是在写程序的过程中却也往往因为不懂内存管理而造成了一些不容易察觉到的内存问题,并且在内存问题出现的时候,也不能很快的定位并解决.因此,了解并掌握Java的内存管理是我们必须要做的是事,也只有这样才能写出更好的程序,更好地优化程序的性能. 概述 Java虚拟机在执行Java程序的过程中会把它所管理的内存划分为若干不同的数据区域,这些区域都有各自的用途以及创建和销毁

Java异常的栈轨迹(Stack Trace)

3.fillInStackTrace() 我们在前面也提到了这个方法.要说清楚这个方法,首先要讲一下捕获异常之后重新抛出的问题.在catch代码块中捕获到异常,打印栈轨迹,又重新throw出去.在上一级的方法调用中,再捕获这个异常并且打印出栈轨迹信息.这两个栈轨迹信息会一样吗?我们看一下代码: public class TestPrintStackTrace {     public static void f() throws Exception{         throw new Exce