【Java】【JVM】内存模型
程序计数器
- 较小内存空间
- 线程私有
- 当前线程行号指示器
- 执行native,则为空undefined
- 唯一没有规定任何OutOfMemoryError区域
- 虚拟机的概念模型
虚拟机栈
- 线程私有,描述方法执行的内存模型
- 存放方法运行时所需数据(局部变量表、操作数栈、动态链接、方法出口等),成为栈帧Stack Frame
- 局部变量表:一组变量值存储空间,基本类型存值,引用类型存地址
- 操作数栈:表达式求值计算
- 动态链接:保存指向当前方法所在类的运行时常量池,调用其他方法时直接从常量池中获取符号引用,转为直接引用使用
- 方法出口:return返回地址
- 其他附加信息
- 执行Java方法(字节码)服务
- 栈深度大于虚拟机允许,抛出StackOverflowError异常(栈深度可以理解为方法调用层级数)
- 扩展得不到足够内存,抛出OutOfMemoryError异常
本地方法栈
- 类似虚拟机栈,但执行Native方法服务
- 会存在StackOverflowError和OutOfMemoryError异常
堆Heap
- 内存最大,被所有线程共享,虚拟机启动时创建
- 唯一目的,存放对象实例
- 垃圾收集器管理的主要区域,也称GC堆(Garbage Collectied Heap)
- 细分:新生代、老年代;Eden空间、From Survior空间、To Survior空间
- 线程共享堆可划分出多个线程私有的分配缓冲区(Thread Local Allocation Buffer TLAB)
- 若堆中没有内存完成实例分配,且无法扩展,抛出OutOfMemoryError异常
对象创建及内存分配过程
- new新对象时放入新生代中Eden区域(大对象直接进入老年代)
- 当新生代满时,触发Minor GC,将幸存对象移入幸存第一区域
- 再次Minjor GC时,存活对象移入幸存第二区域
- 当存活对象超过多次Minjor GC时,进入老年代,默认15次
- 当老年代内存不足时,触发Major GC
- 若老年代GC后内存仍不足,会触发Full GC,再次不足则报OOM错误
- Full GC触发时机:System.gc调用,老年代或方法区内存不足时
TLAB机制
避免多线程操作同一地址,使用加锁机制影响分配速率。建立线程本地分配缓存区,即每个线程专有的内存分配区域。
字符串常量池
在类加载完成,经过验证,准备阶段之后在堆中生成字符串对象实例,然后将该字符串对象实例的引用值存到string pool中。
提示:JDK7之前在永久区(方法区)。
方法区Method Area
- 线程共享区域,存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码和运行时常量池等(类结构信息)
- 是堆的逻辑部分,即Non-Heap非堆
- 也称永久区,Java8已取消该区(改元空间,数据拆分到了Java堆和本地内存)
- 字面量(字符串常量)转移到堆
- 类的静态变量转移到堆
- 符号引用转移至本地内存
- 内存回收目标主要针对常量池的回收和堆类型的卸载
- 可抛出OutOfMemoryError异常
- 内存分配可不连续,可动态扩展
- 并非一直永久,只是相对,垃圾回收率低
运行时常量池 Runtime Constant Pool
- 方法区的一部分
- Class文件常量池(非运行时常量池,编译阶段就已确定)信息:Constant Pool table
- 存放编译器生成的各种字面量和符号引用(及这些符号翻译的直接引用),在类加载后进入方法区的运行时常量池中存放
- 可抛出OutOfMemoryError异常
- 存储java文件常量池中的符号信息
- 相对class常量池,具有动态性,并非只有class常量池唯一数据来源,运行时产生的常量也可以存放进运行时常量池
直接内存Direct Memory
- 非虚拟机运行时数据区的一部分,非规范中定义的内存区域
- 被频繁使用
- 可抛出OutOfMemoryError异常
- 本机直接内存的分配不受堆大小限制,受本机总内存大小及处理器寻址空间的限制
- 可设置-Xmx信息,忽略直接内存
参考:
- 官方文档jdk8
- 网络博文整理