JVM内存区域划分总结

  • JVM 内存区域分为下面几个方面,程序计数器、Java虚拟机栈(Java栈)、Java堆、方法区、运行时常量池、本地方法栈。

1.程序计数器

  • 程序计数器会存储当前线程正在执行的Java方法的JVM指令地址(字节码指令地址)。在 JVM规范中,每个线程都有它自己的程序计数器,并且任何时间一个线程都只有一个方法在执行,也就是所谓的当前方法。
  • 程序计数器的作用
1
2
字节码解释器通过改变程序计数器来依次读取指令,从而实现代码的流程控制。
在多线程情况下,程序计数器记录的是当前线程执行的位置,从而当线程切换回来时,就知道上次线程执行到哪了。
  • 程序计数器的特点
1
2
3
4
是一块较小的内存空间。
线程私有,每条线程都有自己的程序计数器。
生命周期:随着线程的创建而创建,随着线程的结束而销毁。
是唯一一个不会出现OutOfMemoryError的内存区域。

2.Java 虚拟机栈(Java Virtual Machine Stack)

  • Java 虚拟机栈是描述 Java 方法运行过程的内存模型。 早期也叫 Java 栈。每个线程在创建时都会创建一个虚拟机栈,其内部保存一个个的栈帧(Stack Frame),对应着一次次的 Java 方法调用。

  • 栈帧中存储着局部变量表、操作数(operand)栈、动态链接、方法正常退出或者异常退出的定义等

  • Java 虚拟机栈的特点

1
2
3
4
5
1. 局部变量表随着栈帧的创建而创建,它的大小在编译时确定,创建时只需分配事先规定的大小即可。在方法运行过程中,局部变量表的大小不会发生改变。
2. Java 虚拟机栈会出现两种异常:StackOverFlowError 和 OutOfMemoryError。
StackOverFlowError 若 Java 虚拟机栈的大小不允许动态扩展,那么当线程请求栈的深度超过当前 Java 虚拟机栈的最大深度时,抛出 StackOverFlowError 异常。
OutOfMemoryError 若允许动态扩展,那么当线程请求栈时内存用完了,无法再动态扩展时,抛出 OutOfMemoryError 异常。
3. Java 虚拟机栈也是线程私有,随着线程创建而创建,随着线程的结束而销毁。

3.本地方法栈

  • 本地方法栈是为 JVM 运行 Native 方法准备的空间,由于很多 Native 方法都是用 C 语言实现的,所以它通常又叫 C 栈。
  • 它与 Java 虚拟机栈实现的功能类似,只不过本地方法栈是描述本地方法运行过程的内存模型。

4.堆(Heap)

  • 堆是用来存放对象的内存空间,几乎所有的对象都存储在堆中
  • 堆的特点
1
2
3
4
线程共享,整个 Java 虚拟机只有一个堆,所有的线程都访问同一个堆。而程序计数器、Java 虚拟机栈、本地方法栈都是一个线程对应一个。
在虚拟机启动时创建。
是垃圾回收的主要场所。
进一步可分为:新生代、老年代

5.方法区(Method Area)

  • Java 虚拟机规范中定义方法区是堆的一个逻辑部分,这也是所有线程共享的一块内存区域,用于存储所谓的元(Meta)数据,例如类结构信息,以及对应的运行时常量池、字段、方法代码等

6.运行时常量池

  • 常量池是方法区里的一部分, 方法区中存放:类信息、常量、静态变量、即时编译器编译后的代码。常量就存放在运行时常量池中

内存结构图

  • 产生OOM的地方:除了程序计数器之外,都会产生相关的OOM,最主要是在堆上。还有一种是直接内存,也是造成OOM的原因。

直接内存

  • 内存对象分配在JVM中堆以外的内存称为直接内存,这些内存直接受操作系统管理(而不是JVM),这样做的好处是能够在一定程度上减少垃圾回收对应用程序造成的影响。一般我们使用Unsafe和NIO包下ByteBuffer来创建堆外内存

  • 为什么使用堆外内存:

1
2
3
4
1、减少了垃圾回收
使用堆外内存的话,堆外内存是直接受操作系统管理( 而不是虚拟机 )。这样做的结果就是能保持一个较小的堆内内存,以减少垃圾收集对应用的影响。
2、提升复制速度(io效率)
堆内内存由JVM管理,属于“用户态”;而堆外内存由OS管理,属于“内核态”。如果从堆内向磁盘写数据时,数据会被先复制到堆外内存,即内核缓冲区,然后再由OS写入磁盘,使用堆外内存避免了这个操作

堆外内存申请(了解)

  • DK的ByteBuffer类提供了一个接口allocateDirect(int capacity)进行堆外内存的申请,底层通过unsafe.allocateMemory(size)实现。Netty、Mina等框架提供的接口也是基于ByteBuffer封装的。
1
2
3
4
5
6
7
import java.nio.ByteBuffer;
public class DirectOom {
public static void main(String[] args) {
//直接分配128M的直接内存(100M)
ByteBuffer bb = ByteBuffer.allocateDirect(128*1024*1204);
}
}
  • 直接内存(direct memory)不属于JVM运行时数据区的一部分,属于堆外内存,会被频繁使用,因此在设置各个内存范围时要留出一部分物理内存,否则也容易抛出OutOfMemoryError