
Java虚拟机栈(Java Vitual Mechine Stack)
在之前的JVM内存模型一文已经对java虚拟机栈又一个初步的概述了,现在我们再来看看关于栈帧的一些相关内容。
我们知道,栈是线程私有的,每个方法被调用时会在当前线程栈内存中创建一个栈帧,如下图:
栈帧(Stack Frame)
- 栈帧(Stack Frame)是用于支持虚拟机进行方法调用和方法执行的数据结构,它是虚拟机运行时数据区中的java虚拟机栈的栈元素。
- 栈帧存储了方法的局部变量表、操作数栈、动态连接和方法返回地址等信息。
- 正在被线程执行的方法称为当前线程方法,而该方法的栈帧就称为当前帧,执行引擎运行时只对当前栈帧有效。
- 在编译程序代码的时候,栈帧中需要多大的局部变量表,多深的操作数栈都已经完全确定了,并且写人到方法表的Code属性之中,因此一个栈帧需要分配多少内存,不会受到程序运行期变量数据的影响,而仅仅取决于具体的虚拟机实现。
局部变量表(Local Variable Table)
局部变量表(Local Variable Table)是一组变量值存储空间,用于存放方法参数和方法内部定义的局部变量。在Java程序编译为Class文件时就在方法的code属性的max_locals数据项中确定了该方法所需要分配的局部变量表的最大容量。- 局部变量槽(Variable Slot)
局部变量表的容量以变量槽为最小单位。每个变量槽都可以存储32位长度的内存空间,例如boolean、byte、char、short、int、float、reference(对象实例的引用) 和 returnAddress类型(它指向了一条字节码指令的地址)。对于64位长度的数据类型(long,double),虚拟机会以高位对齐方式为其分配两个连续的Slot空间,也就是相当于把一次long和double数据类型读写分割成为两次32位读写。
- 局部变量槽(Variable Slot)
操作数栈(Operand Stack)
操作数栈也同局部变量表一样,操作数栈的最大深度也在编译的时候写人到code属性的max_stacks数据项中。操作数栈的每一个元素可以是任意的Java数据类型,包括long和double。32位数据类型所占的栈容量为1,64位数据类型所占的栈容量为2。在方法执行的任何时候,操作数栈的深度都不会超过在maxstacks数据项中设定的最大值。动态链接(Dynamic Linking)
Class文件的常量池中存有大量的符号引用,字节码中的方法调用指令就以常量池中指向方法的符号引用作为参数。这些符号引用一部分会在类加载阶段或者第一次使用的时候就转化为直接引用,这种转化称为静态解析。另外一部分将在每一次运行期间转化为直接引用,这部分称为动态连接。方法出口
当一个方法开始执行后,只有2种方式可以退出这个方法:方法返回指令: 执行引擎遇到一个方法返回的字节码指令,这时候有可能会有返回值传递给上层的方法调用者,这种退出方式称为正常完成出口。
异常退出: 在方法执行过程中遇到了异常,并且没有处理这个异常,就会导致方法退出。
无论采用任何退出方式,在方法退出之后,都需要返回到方法被调用的位置,程序才能继续执行,方法返回时可能需要在栈帧中保存一些信息。一般来说,方法正常退出时,调用者的PC计数器的值可以作为返回地址,栈帧中会保存这个计数器值。而方法异常退出时,返回地址是要通过异常处理器表来确定的,栈帧中一般不会保存这部分信息。
Java堆(Java Heap)
JVM中只有一个堆,供所有线程共享,java堆可以细分为年轻代(Young Generation)、老年代(Old Generation)和永久代(permanent Generation),其中新生代又分为伊甸园区(Eden Space)、幸存0区(Survivor0 Space)、幸存1区(Survivor1 Space)。永久代是JDK1.7以前,对JVM方法区的实现,在《深入理解JVM》一书中有如下描述: 虽然Java虚拟机规范把方法区描述为堆的一个逻辑部分,但是它却有一个别名叫做Non-Heap(非堆),目的应该是与Java堆区分开来。在JDK1.8以后,移除了永久代,改用元空间(Meta Space)来实现方法区,元空间放在直接内存中。
- JDK1.7堆内存结构图

- JDK1.8 堆内存结构图

年轻代(Young Generation)
在整个Java Heap中,年轻代所占用内存大小的比例是1/3
- 伊甸园区(Eden Space)
整个年轻代可以分成10份,伊甸园区所占内存大小是8/10。大多数情况下,新生的对象会在Eden区分配内存,当Eden区内存不足的时候,会触发一次轻GC(Minor GC)。 - 幸存0区和幸存1区(Survivor0 Space和Survivor1 Space)
幸存0区和幸存1区也叫做幸存From区(SurvivorFrom)和幸存To区(SurvivorTo),Eden、SurvivorFrom和SurvivorTo内存比例是8:1:1。SurvivorFrom中保存了上一次MinorGc中幸存的对象,SurvivorTo区中会保存本次MinorGc中Eden Space和SurvivorFrom中幸存的对象,MinorGc过后,SurvivorFrom区对象被清空,那么下一次MinorGc时,他就会变成SurvivorTo区,而上一次的SurvivorTo则成为SurvivorFrom区。
老年代(Old Generation)
在整个Java Heap中,年轻代所占用内存大小的比例是2/3
在默认情况下,如果幸存区中有对象经过了15次MinorGc还没被回收,这个对象就会就进入老年代,对象进入老年代所经过MinorGc的次数可以通过JVM提供的 -XX:MaxTenuringThreshold 参数指定。另外,上面说了大多数情况下,新生的对象会在Eden区分配内存,但是这不包括大对象,大对象是直接进入老年代的。所谓大对象是指,需要大量连续内存空间的java对象,最典型的大对象就是那种很长的字符串或者数组,虚拟机提供了一个 -XX:PretenureSizeThreshold 参数,令大于这个设置值的对象直接在老年代分配内存。
除了以上情况,《深入理解JVM》中还有如下描述:为了能更好地适应不同程序的内存状况,虚拟机并不是永远的要求对象的年龄必须达到了MaxTenuringThreshold才能晋升老年代,如果Survivor空间中相同年龄的所有对象大小的总和大于Survivor空间的一半,年龄大于或者等于该年龄的对象就可以直接进入老年代,无需等到MaxTenuringThreshold中要求的年龄
本文链接:https://highphone.xyz/9a65291d.html