对Java堆栈的认识

当程序运行时,主要有以下几个地方可以存取数据(摘自Java编程思想)。

寄存器

这是最快的存储区域,因为它位于和其他所有保存方式不同的地方:处理器内部。但是寄存器的数量十分有限,所以寄存器是根据需要由编译器分配。我们对此没有直接的控制权,也不可能在自己的程序里找到寄存器存在的任何踪迹。

栈(先进后出)

驻留于常规RAM(随机访问存储器)区域,可通过它的“栈指针”获得处理的直接支持。栈指针若向下移,会创建新的内存;若向上移,则会释放那些内存。这是一种特别快、特别有效的数据保存方式,仅次于寄存器。但缺点是创建程序时,Java 编译器必须准确地知道栈内保存的所有数据的“长度”以及“存在时间”,缺乏灵活性。这是由于它必须生成相应的代码,以便向上和向下移动指针。

堆(队列优先,先进先出)

一种常规用途的内存池(也在RAM区域),其中保存了Java对象。堆的优势是可以动态地分配内存大小,生存期也不必事先告诉编译器,Java的垃圾收集器会自动收走那些不再使用的堆中的数据。创建一个对象时,只需用new命令编写相关的代码即可。因此,用堆保存数据时会得到更大的灵活性。这种灵活性付出的代价是:在堆中分配存储空间会比较慢。

静态存储

这儿的“静态”(Static)是指“位于固定位置”(尽管也在RAM里)。程序运行期间,静 态存储的数据将随时等候调用。可用static关键字指出一个对象的特定元素是静态的。但 Java 对象本身永远都不会置入静态存储空间。

常量存储

常量值通常直接置于程序代码内部。这样做是安全的,因为它们永远都不会改变。有时,在嵌入式系统中,常量本身会和其他部分分隔离开,所以在这种情况下,可以选择将其放在ROM(只读存储器)中。

非RAM存储

若数据完全独立于一个程序之外,则程序不运行时仍可存在,并在程序的控制范围之外。 其中两个最主要的例子便是“流式对象”和“持久化对象”。对于流式对象,对象会变成字节流,通常会发给 另一台机器。而对于持久化对象,对象保存在磁盘中。即使程序中止运行,它们仍可保持自己的状态不变。对于这些类型的数据存储,一个特别有用的技巧就是它们能存在于其他媒体中。一旦需要,可以将它们恢复成普通的、基于RAM的对象。

在Java中什么时候数据会存在栈里?

栈:当在一段代码块中定义一个变量时,java就在栈中为这个变量分配内存空间,当超过变量的作用域后,Java会自动释放掉为该变量分配的内存空间。

比如下面这段代码:

1
2
3
int i = 0;
char c = 'c';
boolean b;

Java中只要定义了一个“变量”,这个“变量”就会被分配在栈中。比如上面的 i、c、b。像int,char,boolean这种基本类型,“=”号右边的值也是存在栈中的,然后这个“变量”就赋值为右边的“值”了。这种简单的“值”如果去 new 一个出来,往往不是很高效的做法。所以在Java中,也和C,C++一样,创建一个并非是引用的“自动”变量,这些变量直接存储“值”,并放在栈中,所以更加高效。这些“变量”定义在作用域里面,超出作用域后,“变量”就消失了(即出栈)

上图可以看到基本类型中的变量就变成了一个明确的”值“。

存放在堆里的数据

堆:用于存放数组和由 new 创建的对象。在堆中分配的内存,由Java垃圾回收器来自动回收管理。在堆中生成了一个对象后,还会在栈中生成一个指向堆中的对象的一个“地址值”,此时“变量”就等于堆中生成对象的“地址值”,此后可以根据栈中的这个“地址值”找到在堆中的对象并修改对象的属性值。

比如下面这段代码:

1
2
3
4
int[] intArr = {1,2,3,4,5};
String[] strArr = new String[]{"a","b","c"};
User user = new User();
Map map = new HashMap();

上面讲到了变量是在栈内存中分配的,然后在程序运行到作用域外释放。而数组和对象本身在堆内存中分配,即使程序运行到作用域外,数组和对象本身占用的堆内存也不会被马上释放,但此时已经没有变量指向它们了,就变成了垃圾,不能再被使用,随后被垃圾回收器释放掉。

上图可以看到引用类型的变量就变成了指向堆内存中的一个”地址值“,本质上讲也是一个”值“。