JVM-运行时数据区
JVM——运行时数据区
运行时数据区包含:PC寄存器、本地方法栈、虚拟机栈、堆、元数据区、CodeCache
1. PC寄存器
存储下一条指令的地址(线程私有)
不存在StackOverflowError和OutofMemoryError
2. 本地方法栈
管理本地方法的调用(线程私有)
存在StackOverflowError和OutofMemoryError
3. 虚拟机栈
线程私有,内部保存一个个栈帧,对应一次次java方法调用
每个栈帧存储着:
局部变量表:用于存储方法参数与定义在方法内部的局部变量
最基本的存储单元是变量槽slot,32位以内的类型占1个slot,64位的占2个slot;若一个局部变量过了其作用域,其slot可复用
操作数栈:用于保存计算过程的中间结果,同时用于计算过程中变量临时的存储空间
动态链接:每个栈帧内部包含一个指向运行时常量池中该栈帧所属方法的引用 (一个栈帧对应一次java方法调用)
当Java源文件编译为class字节码文件时,由于还没运行,所以不知道变量等具体存储在内存中的哪个地方,此时所有变量和引用都只能作为符号引用保存在class文件的常量池中,动态链接就是要将符号引用转化为直接引用
方法返回地址
一些附加信息
4. 堆
线程共享,几乎所有对象示例和数组都应在运行时分配在堆上
几乎所有:若一个对象在方法内定义,只在方法内使用,则认为没有发生逃逸,可以进行栈上分配
4.1 堆内存细分
- JDK7及以前:新生代(Eden+Survivor)+老年代+永久代
- JDK8及以后:新生代(Eden+Survivor)+老年代+元空间
4.2 对象分配过程
- 先分配至Eden(若Eden放不下,则先进行Minor gc,若还是放不下,放入老年代,若老年代也放不下,报错OOM)
- Eden内存不足,触发Minor gc,将不再被引用的对象销毁,幸存下来的移动至Survivor0
- 若此时再次触发Minor gc,上次幸存至Survivor0的若仍幸存,会将它们移动至Survivor1
- 若对象在Survivor0、Survivor1中来回了15次,则会进入到老年代 (15:对象头用一个4位的区域记录对象年龄)
- 老年代内存不足触发Major gc
4.3 堆空间分代思想
大部分对象是临时对象,将他们放在同一位置(Eden),gc时优先回收
4.4 对象内存分配策略
- 优先分配到Eden
- 大对象直接分配到老年代
- 长期存活对象分配到老年代
- 动态年龄判断:Survivor区中相同年龄的对象总和大于Survivor空间一半,年龄 >= 该年龄的对象可直接进入老年代
- 空间分配担保:老年代连续空间大于新生代对象总大小 或 历次晋升平均大小,说明此时进行Minor gc是安全的,就会进行Minor gc
4.5 TLAB(Thread Local Allocation Buffer)
由于堆是内存共享的,且对象实例的创建在JVM中非常频繁,会带来线程不安全的问题。
为了避免多个线程操作同一地址,JVM在Eden区中为每个线程分配了一个私有缓存区域TLAB,当TLAB满了再使用Eden的公共区域
5. 方法区
线程共享,方法区用于存储已被虚拟机加载的类型信息、常量、静态变量、JIT的CodeCache等,永久代和元空间可类比为对方法区这一抽象概念的实现
JDK7及以前称为永久代,JDK8后用元空间取代了永久代,区别在于:永久代使用虚拟机内存,元空间使用本地内存,减少了OOM的概率
JDK7时,字符串常量池StringTable从永久代被调整到堆中,这是因为永久代回收效率低,而开发中有大量字符串被创建