- JVM 内存区域分为下面几个方面,程序计数器、Java虚拟机栈(Java栈)、Java堆、方法区、运行时常量池、本地方法栈。
1.程序计数器
- 程序计数器会存储当前线程正在执行的Java方法的JVM指令地址(字节码指令地址)。在 JVM规范中,每个线程都有它自己的程序计数器,并且任何时间一个线程都只有一个方法在执行,也就是所谓的当前方法。
- 程序计数器的作用
1 | 字节码解释器通过改变程序计数器来依次读取指令,从而实现代码的流程控制。 |
- 程序计数器的特点
1 | 是一块较小的内存空间。 |
2.Java 虚拟机栈(Java Virtual Machine Stack)
Java 虚拟机栈是描述 Java 方法运行过程的内存模型。 早期也叫 Java 栈。每个线程在创建时都会创建一个虚拟机栈,其内部保存一个个的栈帧(Stack Frame),对应着一次次的 Java 方法调用。
栈帧中存储着局部变量表、操作数(operand)栈、动态链接、方法正常退出或者异常退出的定义等
Java 虚拟机栈的特点
1 | 1. 局部变量表随着栈帧的创建而创建,它的大小在编译时确定,创建时只需分配事先规定的大小即可。在方法运行过程中,局部变量表的大小不会发生改变。 |
3.本地方法栈
- 本地方法栈是为 JVM 运行 Native 方法准备的空间,由于很多 Native 方法都是用 C 语言实现的,所以它通常又叫 C 栈。
- 它与 Java 虚拟机栈实现的功能类似,只不过本地方法栈是描述本地方法运行过程的内存模型。
4.堆(Heap)
- 堆是用来存放对象的内存空间,几乎所有的对象都存储在堆中。
- 堆的特点
1 | 线程共享,整个 Java 虚拟机只有一个堆,所有的线程都访问同一个堆。而程序计数器、Java 虚拟机栈、本地方法栈都是一个线程对应一个。 |
5.方法区(Method Area)
- Java 虚拟机规范中定义方法区是堆的一个逻辑部分,这也是所有线程共享的一块内存区域,用于存储所谓的元(Meta)数据,例如类结构信息,以及对应的运行时常量池、字段、方法代码等
6.运行时常量池
- 常量池是方法区里的一部分, 方法区中存放:类信息、常量、静态变量、即时编译器编译后的代码。常量就存放在运行时常量池中
内存结构图
- 产生OOM的地方:除了程序计数器之外,都会产生相关的OOM,最主要是在堆上。还有一种是直接内存,也是造成OOM的原因。
直接内存
内存对象分配在JVM中堆以外的内存称为直接内存,这些内存直接受操作系统管理(而不是JVM),这样做的好处是能够在一定程度上减少垃圾回收对应用程序造成的影响。一般我们使用Unsafe和NIO包下ByteBuffer来创建堆外内存
为什么使用堆外内存:
1 | 1、减少了垃圾回收 |
堆外内存申请(了解)
- DK的ByteBuffer类提供了一个接口allocateDirect(int capacity)进行堆外内存的申请,底层通过unsafe.allocateMemory(size)实现。Netty、Mina等框架提供的接口也是基于ByteBuffer封装的。
1 | import java.nio.ByteBuffer; |
- 直接内存(direct memory)不属于JVM运行时数据区的一部分,属于堆外内存,会被频繁使用,因此在设置各个内存范围时要留出一部分物理内存,否则也容易抛出OutOfMemoryError