不能不了解的 Java 内存模型

1. Java 内存区域

堆、非堆、Java 虚拟机栈(线程私有)、本地方法栈(线程私有)、程序计数器(线程私有)

非堆 虚拟机栈 本地方法栈 程序计数器
英文名称 Heap Non-Heap JVM Stack Native Method Stack Program Counter Register
说明 包含新生代与老年代 又称永久代或方法区 为 Java 方法执行服务 为 Native 方法执行服务 字节码执行时的行号计数器
线程安全 多线程共享 多线程共享 线程私有 线程私有 线程私有
存储数据 对象实例 常量、静态变量、已被JVM加载的类 栈帧 栈帧 字节码执行时的行号计数器

Windows 下,WIN + R 输入 jconsole 可查看进程下多线程内存分配使用情况

不能不了解的 Java 内存模型

图1-1 进程下多线程内存分配与回收图

1.1 程序计数器(线程私有)

  • 设计
    JVM 的多线程执行,通过轮流切换分配逻辑处理器时间的方式来实现的。
    同一时刻,一个逻辑处理器执行一个线程中的指令,线程切换时,为了恢复正确的线程执行指令位置。
    存在较小的一块内存空间,来作为某个线程的字节码执行时的行号计数器,称之为程序计数器。

1.2 虚拟机栈(线程私有)

  • 创建
    执行 Java 方法时创建
  • 存储
    操作数栈,动态链接,方法出口,局部变量表(局部变量表:编译期可知的基本数据类型,对象的引用,指向字节码指令的地址)
  • 设计
    描述方法执行的内存模型;又可以说,是方法执行时的基础数据结构;
    每个方法执行时,会创建一个栈帧存放局部变量表,操作数栈,动态链接,方法出口等信息;
    栈帧在虚拟机栈,从入栈到出栈的过程,即对应着方法调用到执行完成的过程

1.3 本地方法栈(线程私有)

  • 创建
    执行 native 方法时创建
  • 设计
    服务于虚拟机执行 native 方法时

1.4 堆

  • 创建
    虚拟机启动时创建
  • 存储
    存放对象实例
  • 设计
    几乎所有的对象实例,在堆内存区域进行内存分配;GC 主要发生在堆内存区域。
  • 细分
    新生代和老年代;按对象存活周期区分
  • 再细分
    Eden 区域,From Survivor 区域,To Survivor 区域;
    HotSpot 设计上内存分配占比如下 Eden : From Survivor : To Survivor = 8 : 1 : 1;新生代对象垃圾回收算法采用的是复制算法;老年代垃圾回收算法为标记-整理算法;学会了城市垃圾分类,Java 垃圾回收机制我却还不会
    堆中区域划分是为了更好的回收内存和分配内存;比如对应堆存储分为新生代,老年代来说,非堆存储永久代

1.5 非堆

方法区又称非堆,又可称为永久代

  • 存储
    已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等
  • 设计
    同为线程共享区域,取名非堆,即为了与堆区分开;

2. 补充说明

2.1 运行时常量池

  • 归属
    非堆
  • 存储
    编译期间生成的各种字面量和符号引用,运行期间生成的新常量。
    类加载后进去方法区的运行时常量池。

2.2 运行时数据区域

  • 设计
    Java 内存区域,JVM 执行程序时的数据区域;划分为新生代,老年代,永久代,虚拟机栈,本地方法栈,程序计数器。

2.3 非 Java 堆

Java 内存区域的堆,称为 Java 堆;非 Java 堆又称为 native 堆

  • 归属
    不属于 Java 内存区域
  • 设计
    Java 堆的数据传输到外界,需要把 Java 堆复制到非 Java 堆。

2.4 直接内存

  • 归属
    不属于 JVM 内存区域
  • 设计
    IO(Input/Output) 中的通道与缓冲区可以使用 Native 库直接分配堆外内存,这块堆外内存称为直接内存。
    Java 堆的 DirectByteBuffer 对象可以引用直接内存进行操作,避免了 Java 堆与 Native 堆来回复制数据。

2.5 小结

(1) 程序计数器,Java 虚拟机栈,本地方法栈是线程私有,不存在线程安全问题;
(2) JVM 内存模型中仅程序计数器不会出现 OutOfMemoryError;
(3) Java 虚拟机栈是为 JVM 执行 Java 方法服务的;本地方法栈是为 JVM 执行 native 方法服务的;
(4) Java 虚拟机栈和本地方法栈会出现 OutOfMemoryError 和 StackOverflowError;
(5) 堆中区域划分是为了更好的回收内存和分配内存;
(6) native 库采用 C/C++ 来实现;
(7) Native Method Stack 属于 Java 内存区域;Native Heap 属于 Native 库;(这个理解不正确的话,欢迎指出问题评论区探讨下 Native 堆)
(8) Java 内存区域,JVM 执行程序时的数据区域;划分为新生代,老年代,永久代,虚拟机栈,本地方法栈,程序计数器;

此图收尾,经典谢幕。
不能不了解的 Java 内存模型

图2-1 Java 内存模型图

Power By niaonao