java栈组成_Java栈与栈上分配

本文详细介绍了Java栈的结构、组成以及栈上分配技术。栈是线程私有的,由帧组成,每次函数调用会产生一个栈帧,包括局部变量表、操作数栈和帧数据区。栈溢出可能发生在递归调用过深或局部变量过多时。栈上分配是通过逃逸分析判断对象是否在函数内部使用,以优化性能,减少GC压力。
摘要由CSDN通过智能技术生成

一. java栈:

java栈是一块线程私有的内存空间。如果说,java堆和程序数据密切相关,那么java栈就是和线程执行密切相关的。线程执行的基本行为是函数调用,每次函数调用的数据都是通过Java栈传递的。

java heap,java stack 与Javametaspace之间的关系:

a80758913c1d

00001.png

特点:

线程私有

栈由一系列帧组成(因此Java栈也叫做帧栈)

帧保存一个方法的局部变量、操作数栈、常量池指针

每一次方法调用创建一个帧,并压栈

1.栈的结构和组成:

1)栈的结构:

这是一块先进后出的数据结构,只支持出栈和入栈两种操作。在java栈中保存的主要内容是栈帧。每一次函数调用都会有一个相应的栈帧入栈,每个函数调用结束,都有一个栈帧弹出java栈。当前正在执行的函数对应的栈就是当前的帧(位于栈顶)。

每个栈帧中,至少包含局部变量表,操作数栈和帧数据区几个部分。

注意由于每次函数调用都会生成栈帧并占有一定的栈空间。因此如果栈空间不足,函数调用就无法进行下去。系统就会抛出StackOverflowOver栈溢出的错误。例如递归时,会有很多栈帧入栈。jvm提供了-Xss来指定线程的最大栈空间,这个参数决定了函数调用的深度。

2)栈组成:

栈由栈帧组成,栈帧由局部变量表,操作数栈,帧数据区组成。

局部变量表:

用于保存函数的参数(实参)变量和局部变量。局部变量表中的变量只在当前函数调用中有效,当函数调用结束后,随着函数栈帧的销毁,局部变量表也会随之销毁。

操作数栈:

栈帧的一部分,也是个先入先出的数据结构。用于计算过程的中间结果,同时作为计算过程中变量临时的存储空间。

public static int add(int a,int b){

int c=0;

c=a+b;

return c;

}

调用函数的过程:

0: iconst_0 // 0压栈

1: istore_2 // 弹出int,存放于局部变量2

2: iload_0 // 把局部变量0压栈

3: iload_1 // 局部变量1压栈

4: iadd //弹出2个变量,求和,结果压栈

5: istore_2 //弹出结果,放于局部变量2

6: iload_2 //局部变量2压栈

7: ireturn //返回

a,b变量的值分别是100和98,以下是操作数栈的工作原理以及和局部变量表的关系:

a80758913c1d

00002.jpeg

帧数据区:

栈帧需要数据开支持常量池解析,正常方法返回和异常处理等

以下的例子是个递归,没有递归的出口,会出现栈溢出,并打印递归的深度。

public class TestStackDeep {

private static int count=0;

public static void recursion(long a,long b,long c){

long e=1,f=2,g=3,h=4,i=5,k=6,q=7,x=8,y=9,z=10;

count++;

System.out.println(count);

recursion(a, b, c);

}

public static void recursion(){

count++;

System.out.println(count);

recursion();

}

public static void main(String[] args) {

try {

// recursion(0L,0L,0L);

recursion();

}catch (Exception e){

System.out.println("deep of calling="+count);

e.fillInStackTrace();

}

}

}

影响栈空间使用的因素:

1.阐述列表的参数多。

2.递归的深度过深了。

-Xss256k:

deep of calling=568

递归调用了568次

-Xss512k:

deep of calling=3030

递归调用了568次

Exception in thread "main" java.lang.StackOverflowError

栈溢出,栈的空间满了。可以通过减少参数或局部变量的个数,减少栈空间的占用,达到函数多调用几次的目的。

调用recursion(a, b, c);-Xss256k:

最大深度716

调用调用recursion(),-Xss256k:

最大深度1963

可以看到在相同的栈容量下,局部变量少的函数可以支持更深的函 数调用。

二.栈上分配:

栈上分配是jvm的一个优化技术,对于那些线程私有的对象,可以将它们分配在栈上,而不是堆上。栈上分配的好处是可以在函数调用后自行销毁,而不是GC介入,从而提升了系统的性能。

栈上分配的基础是逃逸分析,逃逸分析的目的是判断对象的作用域是否有可能逃逸出函数体。

函数alloc()内的变量b是线程私有的局部变量,

public class OnStackTest {

public static void alloc(){

byte[] b=new byte[2];

b[0]=1;

}

public static void main(String[] args) {

long b=System.currentTimeMillis();

for(int i=0;i<100000000;i++){

alloc();

}

long e=System.currentTimeMillis();

System.out.println(e-b);

}

}

第一种运行方式:-server -Xmx10m -Xms10m -XX:+DoEscapeAnalysis -XX:+PrintGC

这种方式new对象在栈上分配,gc不参与回收,因为变量仅仅在栈上分配空间,降低gc的工作量,同时防止堆上的空间被占用

输出结果 5 效率很高。

第二种运行方式:-server -Xmx10m -Xms10m -XX:-DoEscapeAnalysis -XX:+PrintGC

这种方式new对象在java堆上分配,gc参与释放

输出结果:

……

[GC 3550K->478K(10240K), 0.0000977 secs]

[GC 3550K->478K(10240K), 0.0001361 secs]

[GC 3550K->478K(10240K), 0.0000963 secs]

564

GC的效率明显低于栈上分配对栈帧的销毁的效率。

小对象(一般几十个bytes),在没有逃逸的情况下,可以直接分配在栈上

直接分配在栈上,可以自动回收,减轻GC压力

大对象或者逃逸对象无法栈上分配

逃逸分析:

下面的代码显示了一个逃逸的对象:因为代码中的User的作用域是整个Main Class,所以user对象是可以逃逸出函数体的。

public class PartionOnStack {

static class User{

private int id;

private String name;

public User(){}

}

private static User user;//在这里逃逸

public static void foo() {

user=new User();

user.id=1;

user.name="sixtrees";

}

public static void main(String[] args) {

foo();

}

}

下面的代码展示的则是一个不能逃逸的代码段。(不能逃逸的才能栈上分配)

public class PartionOnStack {

class User{

private int id;

private String name;

public User(){}

}

public void foo() {

User user=new User();

user.id=1;

user.name="sixtrees";

}

public static void main(String[] args) {

PartionOnStack pos=new PartionOnStack();

pos.foo();

}

}

总结:

*小对象(一般几十个bytes),在没有逃逸的情况下,可以直接分配在栈上

*直接分配在栈上,可以自动回收,减轻GC压力

*大对象或者逃逸对象无法栈上分配

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值