JVM内存区域(重要!)
分为线程共享和线程私有的两种内存:
- 线程私有
- 程序计数器(唯一不会出现
OutOfMemoryError
内存溢出错误的区域) - 虚拟机栈
- 本地方法栈
- 程序计数器(唯一不会出现
- 线程共享
- 堆
- 方法区(1.8之后去掉改为本地内存内的元空间)
- 运行时常量池
- 本地内存
- 直接内存
- 元空间
下面详细解释一下:
程序计数器
- 唯一不会出现
OutOfMemoryError
内存溢出错误的区域 - 记录线程的暂停与继续执行的指令行
- 分支/循环/跳转等流程控制
虚拟机栈
- 传递调用方法时的数据
- 调用方法,栈帧入栈;方法结束(返回值/异常),栈帧出栈
- 局部变量表:编译期可知的数据类型/对象引用
- 两种错误
- StackOverFlowError:JVM虚拟机栈容量不允许动态扩展
- OutOfMemoryError:JVM虚拟机栈容量允许动态扩展但是超出内存
本地方法栈
其实与JVM栈一样,不过调用的是Native本地方法
线程共享
堆
- 最大的一块
- 存放几乎所有对象实例及数组(JDK1.7之后不再那么绝对因为有逃逸分析)
因为是垃圾回收主要负责的一块,也被称为GC堆。
下面内容涉及垃圾回收的东西了。
可以根据垃圾回收继续细分:
在 JDK 7 版本及 JDK 7 版本之前,堆内存被通常被分为下面三部分:
- 新生代内存(Young Generation)
- 老生代(Old Generation)
- 永生代(Permanent Generation)
JDK 8 版本之后方法区(HotSpot 的永久代)被彻底移除了(JDK1.7 就已经开始了),取而代之是元空间,元空间使用的是直接内存。
1 | graph LR |
大部分情况,对象都会首先在 Eden 区域分配,在一次新生代垃圾回收后,如果对象还存活,则会进入 s0 或者 s1,并且对象的年龄还会加 1(Eden 区->Survivor 区后对象的初始年龄变为 1),当它的年龄增加到一定程度(默认为 15 岁),就会被晋升到老年代中。对象晋升到老年代的年龄阈值,可以通过参数 -XX:MaxTenuringThreshold
来设置。
“Hotspot 遍历所有对象时,按照年龄从小到大对其所占用的大小进行累积,当累积的某个年龄大小超过了 survivor 区的一半时,取这个年龄和 MaxTenuringThreshold 中更小的一个值,作为新的晋升年龄阈值”。
- 新生代
- 新生代GC过程称为MinorGC,采用复制算法
- 老年代
- 存放长生命周期对象,大对象(默认2KB~128KB)
- 老年代的CG过程称为MajorGC,采用标记-清除算法,不会频繁触发
方法区
(1.8之后去掉改为本地内存内的元空间)
方法区其实在Hotspot虚拟机中就是永久代(1.8之前),在1.8之后就是元空间。
方法区与 Java 堆一样,是各个线程共享的内存区域。
它用于存储已被虚拟机加载的Class类信息、常量、静态变量、即时编译器编译后的代码等数据。
方法区有JVM内存上限限制,元空间只有系统限制。
运行时常量池
JDK1.7之前,常量池全在永久代。
JDK1.7,字符串常量池在堆,运行时常量池在永久代
JDK1.8之后,字符串常量池在堆,运行时常量池在元空间。
直接内存
直接内存并不是虚拟机运行时数据区的一部分,也不是虚拟机规范中定义的内存区域,但是这部分内存也被频繁地使用。而且也可能导致 OutOfMemoryError 错误出现。
本机直接内存的分配不会受到 Java 堆的限制,但是,既然是内存就会受到本机总内存大小以及处理器寻址空间的限制。
总结一下:
- 线程私有
- 程序计数器(流程控制,线程暂停)
- 虚拟机栈(栈帧,方法调用)
- 本地方法栈(Native方法)
- 线程共享
- 堆(包含字符串常量池,最大的内存区域,垃圾回收主要管理区域,存储对象/数组)
- 方法区(永久代,后被替换为元空间)
- 本地内存
- 元空间(包含运行时常量池)
- 直接内存(系统内存)