1. 概览
线程私有的区域是用来运行指令的,线程共享的区域是用来存储数据的。
2. 线程私有区域
线程私有区域是用来运行指令的。
2.1 程序计数器
程序计数器指向当前线程正在运行的字节码指令的地址(行号)。
为什么需要程序计数器?
Java 是多线程的,在运行过程中需要线程切换,程序计数器保证在多线程情况下程序正常执行。
程序计数器是 JVM 中唯一不会发生 OOM 的内存区域。
2.2 栈
为什么 JVM 要使用栈?
可以很好地兼容方法调用方法这一个过程。
1. 虚拟机栈
大小设置:-Xss。
存储当前线程运行方法所需的数据、指令、返回地址。
虚拟机栈中包含着一个个的栈帧,栈帧包括:
- 局部变量表
- 操作数栈
- 动态连接
- 返回地址
- …
动态连接是为多态服务的。
栈的深度是有限制的。
虚拟机栈会产生 OOM:
2. 本地方法栈
本地方法栈保存的是 native 方法的信息。当 JVM 创建的线程调用 native 方法后,JVM 不再为其在虚拟机栈中创建栈帧,只是简单地动态连接并直接调用 native 方法。
虚拟机规范无强制规定如何实现,各版本虚拟机自由实现,HotSpot 直接将本地方法栈与虚拟机栈合二为一。
3. 线程共享区域
线程共享区域是用来存储数据的。
3.1 堆
大小设置:-Xms、-Xmx、-Xmn。
堆涉及到内存的分配(new、反射)与回收(回收算法、收集器)。
几乎所有的对象都在堆中分配,有一些会进行逃逸分析在栈上分配。
3.2 方法区
方法区内有类信息、常量、静态变量、即时编译期编译后的代码。
实现方式:在 JDK 1.7 以前叫永久代,在 JDK 1.8 以后叫元空间。
永久代参数:-XX:PerSize、-XX:MaxPermSize
元空间参数:-XX:MetaspaceSize、-XX:MaxMetaspaceSize
3.3 运行时常量池
Class 文件中的常量池(编译器生成的各种字面量、符号引用)会在类加载后放入运行时常量池,其中包含字面量、符号引用。
final static String abc = “hello”; 其中 abc 在方法区,hello 在运行时常量池。
区域变动:
- 运行时常量池:在 JDK 1.6 运行时常量池在方法区中,在 JDK 1.7 运行时常量池在堆中。
- 永久代:方法区在 JDK 1.7 以前叫永久代,在 JDK 1.8 替换为元空间。
为什么永久代要替换为元空间?
- 永久代来存储类信息、常量、静态变量等数据不是最佳方式,很容易遇到内存溢出问题。
- 对永久代进行垃圾回收是很困难的,元空间和堆的垃圾回收可以隔离,进而避免永久代引发的 Full GC、OOM 问题。
4. 直接内存
直接内存不是虚拟机运行时数据区的一部分,也不是 java 虚拟机规范中定义的内存区域。
如果使用了 NIO,这块区域会被频繁使用,在 java 堆内可以用 directByteBuffer 对象直接引用并操作。
这块内存不受 java 堆大小限制,但受本机总内存的限制,可以通过 MaxDirectMemorySize 来设置(默认与堆内存最大值一样),所以也会出现 OOM 异常。
直接内存避免了在 Java 堆和 Native 堆中来回复制数据,提高效率。