heap 和 stack 有什么区别。栈是一种线形集合,其添加和删除元素的操作应在同一段完成。栈按照后进先出的方式进行处理。堆是栈的一个组成元素 .
堆存储 :heapstorage
堆存储分配: heapstorage allocation
堆存储管理: heap storage management
栈编址: stack addressing
栈变换: stack transformation
栈存储器: stack memory
栈单元: stack cel
在 JVM 中,内存分为两个部分, Stack (栈)和 Heap (堆),这里,我们从 JVM 的内存管理原理的角度来认识 Stack 和 Heap ,并通过这些原理认清 Java 中静态方法和静态属性的问题。
一般, JVM 的内存分为两部分: Stack 和 Heap 。
Stack (栈)是 JVM 的内存指令区。 Stack 管理很简单, push 一定长度字节的数据或者指令, Stack 指针压栈相应的字节位移; pop 一定字节长度数据或者指令, Stack 指针弹栈。 Stack 的速度很快,管理很简单,并且每次操作的数据或者指令字节长度是已知的。所以 Java 基本数据类型, Java 指令代码,常量都保存在 Stack 中。
Heap (堆)是 JVM 的内存数据区。 Heap 的管理很复杂,每次分配不定长的内存空间,专门用来保存对象的实例。在 Heap 中分配一定的内存来保存对象实例,实际上也只是保存对象实例的属性值,属性的类型和对象本身的类型标记等,并不保存对象的方法(方法是指令,保存在 Stack 中) , 在 Heap 中分配一定的内存保存对象实例和对象的序列化比较类似。而对象实例在 Heap 中分配好以后,需要在 Stack 中保存一个 4 字节的 Heap 内存地址,用来定位该对象实例在 Heap 中的位置,便于找到该对象实例。
由于 Stack 的内存管理是顺序分配的,而且定长,不存在内存回收问题 ;而 Heap 则是随机分配内存,不定长度,存在内存分配和回收的问题;因此在 JVM 中另有一个 GC 进程,定期扫描 Heap ,它根据 Stack 中保存的 4 字节对象地址扫描 Heap ,定位 Heap 中这些对象,进行一些优化(例如合并空闲内存块什么的),并且假设 Heap 中没有扫描到的区域都是空闲的,统统 refresh (实际上是把 Stack 中丢失了对象地址的无用对象清除了),这就是垃圾收集的过程;关于垃圾收集的更深入讲解请参考 51CTO 之前的文章《 JVM 内存模型及垃圾收集策略解析》。
JVM 的体系结构
我们首先要搞清楚的是什么是数据以及什么是指令。然后要搞清楚对象的方法和对象的属性分别保存在哪里。
1 )方法本身是指令的操作码部分,保存在 Stack 中;
2 )方法内部变量作为指令的操作数部分,跟在指令的操作码之后,保存在 Stack 中(实际上是简单类型保存在 Stack 中,对象类型在 Stack 中保存地址,在 Heap 中保存值);上述的指令操作码和指令操作数构成了完整的 Java 指令。
3 )对象实例包括其属性值作为数据,保存在数据区 Heap 中。
非静态的对象属性作为对象实例的一部分保存在 Heap 中,而对象实例必须通过 Stack 中保存的地址指针才能访问到。因此能否访问到对象实例以及它的非静态属性值完全取决于能否获得对象实例在 Stack 中的地址指针。
非静态方法和静态方法的区别:
非静态方法有一个和静态方法很重大的不同:非静态方法有一个隐含的传入参数,该参数是 JVM 给它的,和我们怎么写代码无关,这个隐含的参数就是对象实例在 Stack 中的地址指针。因此非静态方法(在 Stack 中的指令代码)总是可以找到自己的专用数据(在 Heap 中的对象属性值)。当然非静态方法也必须获得该隐含参数,因此非静态方法在调用前,必须先 new 一个对象实例,获得 Stack 中的地址指针,否则 JVM 将无法将隐含参数传给非静态方法。
静态方法无此隐含参数,因此也不需要 new 对象,只要 class 文件被 ClassLoader load 进入 JVM 的 Stack ,该静态方法即可被调用。当然此时静态方法是存取不到 Heap 中的对象属性的。
总结一下该过程:当一个 class 文件被 ClassLoader load 进入 JVM 后,方法指令保存在 Stack 中,此时 Heap 区没有数据。然后程序技术器开始执行指令,如果是静态方法,直接依次执行指令代码,当然此时指令代码是不能访问 Heap 数据区的;如果是非静态方法,由于隐含参数没有值,会报错。因此在非静态方法执行前,要先 new 对象,在 Heap 中分配数据,并把 Stack 中的地址指针交给非静态方法,这样程序计数器依次执行指令,而指令代码此时能够访问到 Heap 数据区了。
静态属性和动态属性:
前面提到对象实例以及动态属性都是保存在 Heap 中的,而 Heap 必须通过 Stack 中的地址指针才能够被指令(类的方法)访问到。因此可以推断出:静态属性是保存在 Stack 中的,而不同于动态属性保存在 Heap 中。正因为都是在 Stack 中,而 Stack 中指令和数据都是定长的,因此很容易算出偏移量,也因此不管什么指令(类的方法),都可以访问到类的静态属性。也正因为静态属性被保存在 Stack 中,所以具有了全局属性。
在 JVM 中,静态属性保存在 Stack 指令内存区,动态属性保存在 Heap 数据内存区。