前言
今天我们来谈谈Java的内存机制~
Runtime Data Areas
Runtime Data Areas,运行时数据区,通过上图,我们可以拆分出Java内存管理的如下三大区域——
- 堆(Heap)
- 方法区(Method Area)
- 线程区(Thread)
堆
本内存空间是由程序员控制分配和回收的,我们常用的
new xxx()
语句即为在堆中实例化一个具有xxx()构造方法
的对象
如果程序员忘记对本区域的实例对象
进行回收时,程序结束时系统会自动通过GC(垃圾回收机制)
对本区域进行自动回收
特点
- 线程公有
- 存放对象实例
- 垃圾收集器主要管理区域
- 堆无法再扩展时会出现OutOfMemoryError异常
方法区
本内存空间存储的是
已被虚拟机加载的类信息
、常量
、静态变量
、JIT编译后的代码
等数据,包含运行时的常量池(Runtime Constant Area)
特点
- 线程公有
- 存储类信息、常量、静态变量、即时编译器编译后的代码
- 无法满足内存分配需求时会出现OutOfMemoryError异常
- 包括
运行时常量池
- 运行时常量池
存储编译器生成的各种字面量和符号引用,运行期间也可以将新的常量放入池中。无法满足内存分配需求时会出现OutOfMemoryError异常
线程区
本内存空间存储的是当前线程私有的数据,又被划分为:
虚拟机栈(JVM Stacks)
、本地方法栈(Native Method Stacks)
和程序计数器(Program Counter Register)
虚拟机栈
- 线程私有
- 存放局部变量表、操作数栈、动态链接、方法出口等信息
- 局部变量表
存放方法参数和局部变量- 操作数栈
计算过程的临时存储空间- 动态链接
存储在运行期间会转换为直接引用的符号- 方法出口
包括正常返回出口和异常返回出口- 方法调用对应着栈帧的入栈、出栈
- 栈深度超过虚拟机允许的深度——StackOverflowError异常;动态扩展无法申请到足够的内存,会出现OutOfMemoryError异常
本地方法栈
作用同虚拟机栈
程序计数器
- 程序计数器是线程私有的
- 可看作当前线程执行的虚拟机字节码指令的行号指示器
- Java方法执行时,其值为正在执行的Java方法的字节码地址;Native方法执行时,其值为空
- 无OutOfMemoryError情况
对象
在《疑难杂症_向上/下造型、new、引用、构造方法》一文中我们提到了对象存放在堆中、对象由new+构造器生成,但其具体步骤并不明细
对象的创建过程是从遇到
new关键字
开始的,当JVM遇到new关键字
后,其会先检查后面的构造方法
是否在常量池
中定位一个类
的符号引用
。如果能定位,其会检查类
是否已经被加载、解析和初始化过,如果没有则会完成上述过程。
在检查无误后,JVM会在堆
中为新对象
分配内存,分配的内存空间初始化为0(不含对象头
)
之后,对象
的信息会被放到新对象
的对象头
中,调用构造函数
或初始化文件
对新对象进行初始化
对象的存储结构
- 对象头
包括运行时数据(Mark Word)
和类型指针(指向类元数据)
- Mark Word
包括:哈希码、GC分代年龄、锁状态标志、线程持有的锁、偏向线程ID、偏向时间戳等- 实例数据
程序代码中定义的各种类型的字段内容- 对齐补充
用于将对象大小补全至8字节的整数倍(非必须存在)
GC垃圾回收机制
垃圾收集算法
- 引用计数算法
- 可达性分析算法
- 复制算法
- 标记-整理算法
- 分代收集算法
垃圾收集器
- Serial收集器
- ParNew收集器
- Parallel Scavenge收集器
- Serial Old收集器
- Parallel Old收集器
内存分配与回收策略
- 对象优先在Eden上分配
- 大对象直接进入老年代
- 长期存活的对象进入老年代
- 动态对象年龄判定
- 空间分配担保