JVM内存模型详解
一、JVM整体结构及内存除了堆、方法区线程共享,其余都是线程私有(程序计数器、java虚拟机栈、本地方法栈)。 二、JAVA虚拟机栈java虚拟机栈也就是数据结构中的栈先进后出,只不过栈里面存的都是栈帧栈帧:每个方法在执行时都会创建一个栈帧。栈帧中存储了局部变量表、操作数栈、动态连接和方法出口等信息。每个方法从调用到运行结束的过程,就对应着一个栈帧在栈中压栈到出栈的过程 ## 1、局部变量表局部
一、JVM整体结构及内存
除了堆、方法区线程共享,其余都是线程私有(程序计数器、java虚拟机栈、本地方法栈)。
二、JAVA虚拟机栈
java虚拟机栈也就是数据结构中的栈先进后出,只不过栈里面存的都是栈帧
栈帧:每个方法在执行时都会创建一个栈帧。栈帧中存储了局部变量表、操作数栈、动态连接和方法出口等信息。每个方法从调用到运行结束的过程,就对应着一个栈帧在栈中压栈到出栈的过程
## 1、局部变量表
局部变量表中存储了基本数据类型(boolean、byte、char、short、int、float、long、double)的局部变量(包括参数)、和对象的引用(String、数组、对象等),但是不存储对象的内容。局部变量表所需的内存空间在编译期间完成分配,在方法运行期间不会改变局部变量表的大小。
局部变量表中数据存储在变量槽(Variable Slot),每个变量槽最大存储32位的数据类型。对于64位的数据类型(long、double),JVM 会为其分配两个连续的变量槽来存储
看下局部变量表是不是编译期间分配内存:如下Test该类字节码文件
public class Test {
private void get() {
String a = "a";
System.out.println(a);
String b = "b";
}
private void get1() {
{
String a = "a";
System.out.println(a);
}
String b = "b";
}
}
此时并没有启动程序,此时局部变量表已分配内存,普通方法与 static 方法在第 0 个槽位的存储有所不同。非 static 方法的第 0 个槽位存储方法所属对象实例的引用(此demo中的this)。
再看下get1()方法局部变量表,注意slot中的下标并不是我们期望的0、1、2,此现象称为slot复用:为了尽可能的节省栈帧空间,局部变量表中的 Slot 是可以复用的。方法中定义的局部变量,其作用域不一定会覆盖整个方法。当方法运行时,如果已经超出了某个变量的作用域,即变量失效了,那这个变量对应的 Slot 就可以交给其他变量使用,也就是所谓的 Slot 复用。通过一个例子来理解变量“失效”
2、操作数栈
操作数栈的元素可以是任意的Java数据类型。方法刚开始执行时,操作数栈是空的,在方法执行过程中,通过字节码指令对操作数栈进行压栈和出栈的操作。通常进行算数运算的时候是通过操作数栈来进行的,又或者是在调用其他方法的时候通过操作数栈进行参数传递。操作数栈可以理解为栈帧中用于计算的临时数据存储区
3、动态链接
动态链接是在程序运行期间完成的将符号引用替换为直接引用(就是调用hello()方法的符号引用转为直接内存过程,和类加载静态链接表示静态方法调用符号引用转直接引用)
public static void main(String[] args) {
Test t=new Test();
t.hello();//动态连接,运行期间,符号引用转内存地址过程
}
4、方法出口
方法返回地址,列如上面demo,hello方法栈帧执行完方法出口记录main栈帧中地址回到main方法中位置,
三、程序计数器
一个线程的执行,是通过字节码解释器改变当前线程的计数器的值,来获取下一条需要执行的字节码指令,从而确保线程的正确执行。线程上下文切换,再次回到当前线程位置
四、堆
堆分为年轻代(Young Generation)和老年代(Old Generation);年轻代又分为伊甸园(Eden)和幸存区(Survivor区);幸存区又分为From Survivor空间和 To Survivor空间.
gc时用到的垃圾收集算法(复制、标记清楚、标记整理)
1、对象直接进入老年代
大对象直接进入老年代
大对象就是需要大量连续内存空间的对象(比如:字符串、数组)。JVM参数 -XX:PretenureSizeThreshold 可以设置大
对象的大小,如果对象超过设置大小会直接进入老年代,不会进入年轻代,这个参数只在 Serial 和ParNew两个收集器下
有效。
比如设置JVM参数:-XX:PretenureSizeThreshold=2000000 (单位是字节) -XX:+UseSerialGC ,再执行下上面的第一
个程序会发现大对象直接进了老年代
为什么要这样呢?
为了避免为大对象分配内存时的复制操作而降低效率。
长期存活的对象将进入老年代
既然虚拟机采用了分代收集的思想来管理内存,那么内存回收时就必须能识别哪些对象应放在新生代,哪些对象应放在
老年代中。为了做到这一点,虚拟机给每个对象一个对象年龄(Age)计数器。
对象动态年龄判断
当前放对象的Survivor区域里(其中一块区域,放对象的那块s区),一批对象的总大小大于这块Survivor区域内存大小的50%(-XX:TargetSurvivorRatio可以指定),那么此时大于等于这批对象年龄最大值的对象,就可以直接进入老年代了,
例如Survivor区域里现在有一批对象,年龄1+年龄2+年龄n的多个年龄对象总和超过了Survivor区域的50%,此时就会
把年龄n(含)以上的对象都放入老年代。这个规则其实是希望那些可能是长期存活的对象,尽早进入老年代。对象动态年
龄判断机制一般是在minor gc之后触发的
2垃圾收集算法:
标记-复制算法
为了解决效率问题,“复制”收集算法出现了。它可以将内存分为大小相同的两块,每次使用其中的一块。当这一块的
内存使用完后,就将还存活的对象复制到另一块去,然后再把使用的空间一次清理掉。这样就使每次的内存回收都是对
内存区间的一半进行回收。
标记-清除算法
算法分为“标记”和“清除”阶段:标记存活的对象, 统一回收所有未被标记的对象(一般选择这种);也可以反过来,标
记出所有需要回收的对象,在标记完成后统一回收所有被标记的对象 。它是最基础的收集算法,比较简单,但是会带来
两个明显的问题:
1. 效率问题 (如果需要标记的对象太多,效率不高)
2. 空间问题(标记清除后会产生大量不连续的碎片
标记-整理算法
与“标记-清除”算法一样,但后续步骤不是直接对可回收对象回
收,而是让所有存活的对象向一端移动,然后直接清理掉端边界以外的内存。
五、方法区
用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码
更多推荐
所有评论(0)