JVM 内存区域划分
# Reference
- 深入理解Java虚拟机(第2版) (opens new window) 第2章
- 一文了解JVM (opens new window)
- JVM源码分析之Metaspace解密 (opens new window)
# 堆(Heap)
JVM中最大的内存区域,被所有线程共享。在虚拟机启动时创建,用于存放对象实例。
虚拟机规范要求所有对象实例都要在堆创建,但是随着JIT编译器的发展和逃逸分析技术成熟,也不是所有的对象都是在堆中创建了。
堆内存划分:
JDK1.7中把永久代放到了堆中,JDK1.8用元空间替代了永久代,同时属于本地内存,不在堆中分配
另外,永久代只存在于HotSpot虚拟机,其他虚拟机并没有永久代的概念
Eden:from:to - 8:1:1 New:Old - 1:3
# 直接内存(堆外内存)
直接内存不属于虚拟机规范中的运行时数据区的一部分,在一些场景中可以避免数据在native堆和Java堆中来回复制,显著提高性能。当native堆内存不足时,会调用System.gc(),期望可以回收部分内存(所以最好不要禁用System.gc(),-XX:+DisableExplicitGC),如果内存任然不足,则会导致OutOfMenoryError。
直接内存可以通过调用ByteBuffer.allocateDirect()获得DirectByteBuffer对象来申请,netty中就有使用直接内存来提高性能。
# 方法区(Method-Area / Non-Heap)
方法区也是线程共享的内存区域,用于存储虚拟机加载的类(版本、字段、方法、接口等)、常量、静态变量
很多地方把方法区又叫“永久代”,但是二者并不等价。只是HotSpot虚拟机用永久代来实现了方法区,省去了专门编写方法区内存管理的代码。这点上,虚拟机规范并没有约束如何实现。不过这个方案并不是很好,因为永久代的内存大小不好管理。容易遇到OOM问题,并且其他虚拟机也不存在永久代。而在JDK1.8中,Hotspot也用元空间代替的永久代。
# 运行时常量池(Runtime Constant Pool)
运行时常量池属于方法区的一部分,用于存放编译期生成的各种字面量和符号引用,这部分内容在类加载时进入运行时常量池。Java语言不约束常量一定在编译期才能产生,运行期间也可以吧产生的常量放到运行时常量池中,例如String类的intern()方法,所以常量池无法申请到内存时也会抛出OutOfMemoryError。
# 程序计数器(Program Counter Register)
程序计数器的内存空间很小,用于记录当前线程执行字节码的位置(可以理解成字节码行号),方法的分支、循环、跳转、异常处理(即:程序执行过程),都需要依赖程序计数器。在多线程任务切换过程中也需要从程序计数器加载任务执行的起始位置。
在虚拟机规范中,程序计数器只是一个概念模型。
# 虚拟机栈(VM Stack)
虚拟机栈是线程私有内存空间,随着线程的创建而创建,销毁而回收。用来保存方法执行的局部变量表、操作数栈、动态链接和方法返回地址。每个方法的执行和返回就对应一次入栈和出栈。
# 本地方法栈(Native Method Stack)
本地方法栈与虚拟机栈作用类似,区别在于本地方法栈用于Native方法的调用(例如本地c、c++类库),而虚拟机规范并没有规定本地方法区的具体实现(可以用任何语言实现),Sum Hotspot虚拟机则将虚拟机栈和本地方法栈合并在一起。本地方法栈统一会抛出StackOverflowError和OutOfMemoryError。