1. 概述

Java 虚拟机在执行 Java 程序的过程中会把它管理的内存划分为若干个不同的数据区域。它们各有用途,有些随着虚拟机进程的启动一直存在(堆、方法区),有些则随着用户线程的启动和结束而建立和销毁(程序计数器、虚拟机栈、本地方法栈)。

《Java 虚拟机规范》中规定 Java 虚拟机管理的内存包括以下几个区域:

下面简要分析各个区域的特点。

2. JVM 运行时内存区域

2.1 程序计数器

程序计数器(Program Counter Register),可以看做当前线程所执行的字节码的行号指示器(其实就是记录代码执行到了哪里)。特点如下:

  • 线程私有;
  • 占用内存空间较小;
  • 若线程执行的是 Java 方法,记录的是虚拟机字节码指令地址;若执行的是本地(Native)方法,则为空(Undefined);
  • 该区域是唯一一个在《Java 虚拟机规范》中规定无任何 OutOfMemoryError 的区域。

主要作用:记录线程执行到了哪里。

2.2 Java 虚拟机栈

Java 虚拟机栈(Java Virtual Machine Stacks):Java 方法执行的线程内存模型。

每个方法被执行时,虚拟机栈都会创建一个栈帧(Stack Frame)用于存储局部变量表、操作数栈、动态连接、方法出口等信息。每个方法从被调用直至执行完毕的过程,就对应着一个栈帧在虚拟机栈中从入栈到出栈的过程。其中局部变量表包括:

  • Java 虚拟机基本数据类型(8 种)
  • 对象引用(reference 类型,可能是一个指向对象起始地址的指针)
  • returnAddress

这些数据类型在局部变量表中的存储空间以局部变量槽(Slot)表示,其中 long 和 double 占用两个槽,其他类型占用一个槽。局部变量表所需内存空间在编译期完成分配,当进入一个方法时,该方法需要在栈帧中分配多大的局部变量空间是完全确定的,运行期间不会改变其大小。

虚拟机栈的特点:

  • 线程私有;

  • 生命周期与线程相同;

  • 两类异常

    • 线程请求的栈深度大于虚拟机所允许的深度时抛出 StackOverflowError 异常;

      • 栈扩展时无法申请到足够的内存时抛出 OutOfMemoryError 异常。

主要目的:Java 方法执行的线程内存模型。

2.3 本地方法栈

本地方法栈(Native Method Stacks)与 Java 虚拟机栈作用类似。二者区别:

  • Java 虚拟机栈为 JVM 执行 Java 方法(字节码)服务;
  • 本地方法栈为 JVM 使用到的本地(Native)方法服务。

异常与 Java 虚拟机栈相同。

主要目的:Native 方法执行的线程内存模型。

2.4 Java 堆

对多数应用来说,Java 堆(Java Heap)是 JVM 管理的内存中最大的一块。

唯一目的:存放对象实例(【几乎所有】的对象实例都在这里分配内存)。

《Java 虚拟机规范》描述:所有对象实例及数组都应在堆上分配。

而从实现角度看,由于即使编译技术(尤其是逃逸分析技术的日渐强大),"栈上分配"等手段使得对象并非完全在堆上分配。

特点:

  • 线程共享
  • 虚拟机启动时创建

PS: "新生代"、"老年代"、"Eden 区"等一系列对堆的区域划分,只是部分垃圾收集器的一些共性或设计风格,而非虚拟机的固有内存布局,更非《Java 虚拟机规范》的划分。

将 Java 堆细分的目的只是为了更好地回收内存,或者更快地分配内存。

2.5 方法区

方法区(Method Area):用于存储已被虚拟机加载的类型信息、常量、静态变量、即时编译器编译后的代码缓存等数据,该区域也是线程共享的。又称"非堆"。

与方法区联系密切的一个概念是"永久代",下面简要介绍。

  • 永久代

"永久代(Permanent Generation)",可以理解为 JDK 1.8 之前 HotSpot 虚拟机对《Java 虚拟机规范》中"方法区"的实现。从 JDK 1.6、1.7 到 1.8+,HotSpot 虚拟机的运行时数据区变迁示意图如下:

HotSpot VM JDK 1.6 的运行时数据区示意图如下:

JDK 1.7 中,将 1.6 中永久代的字符串常量池和静态变量等移到了堆中,如下(虚线框表示已移除):

而到了 JDK 1.8,则完全废弃了"永久代",改用了在本地内存中实现的"元空间(Metaspace)",将 JDK 1.7 中永久代剩余的部分(主要是类型信息)移到了元空间,如下(虚线框表示已移除):

从上面几张图可以看出永久代和元空间的主要区别有以下两点:

  1. 存储位置不同

    1. 永久代是 JVM 内存的一部分,元空间在本地内存中(JVM 内存之外);
    2. 永久代使用不当可能导致 OOM,元空间一般不会。
  2. 存储内容不同:元空间存储的是「类型信息」(即类的元信息),而永久代除了类型信息,还包括「字符串常量池」和「静态变量」等(可以理解为元空间是永久代拆分出来的一部分)。

那么问题来了:为什么要把永久代替换为元空间呢?

原因大概有以下几点:

  1. Oracle 收购了两种 JVM:HotSpot VM 和 JRockit VM,并且想要将它们整合,但二者方法区实现差异较大;
  2. 字符串存在永久代中,容易出现性能问题和 OOM;
  3. 类及方法的信息大小较难确定,永久代大小难以确定:太小易导致永久代溢出,太大则易导致老年代溢出(JVM 内存是有限的,此消彼长);
  4. 永久代会为垃圾回收带来不必要的复杂度,且回收效率较低("性价比"低)。

2.6 运行时常量池

运行时常量池(Runtime Constant Pool)是方法区的一部分。

Class 文件中除了有类的版本、字段、方法、接口等描述外信息,还有一项信息是常量池表(Constant Pool Table),用于存放编译期生成的各种字面量和符号引用,这部分内容将在类加载后进入方法区的运行时常量池中存放。

相比于 Class 文件常量池的一个重要特性是「动态性」,运行期间也可以将新的常量放入池中(例如 String 类的 intern() 方法)。

  • 可能产生的异常:OutOfMemoryError。

2.7 直接内存

直接内存(Direct Memory)并非虚拟机运行时数据区的一部分,也非《Java 虚拟机规范》定义的内存区域。但该部分内存被频繁使用(例如 NIO),而且可能导致 OutOfMemoryError。

3. OOM异常实践

3.0 操作系统及 JDK 版本

  • 操作系统:macOS Mojave 10.14.5
  • JDK 1.8
$ java -version
java version "1.8.0_191"
Java(TM) SE Runtime Environment (build 1.8.0_191-b12)
Java HotSpot(TM) 64-Bit Server VM (build 25.191-b12, mixed mode)
  • JDK 1.7
$ java -version
java version "1.7.0_80"
Java(TM) SE Runtime Environment (build 1.7.0_80-b15)
Java HotSpot(TM) 64-Bit Server VM (build 24.80-b11, mixed mode)

3.1 Java 堆溢出

  • 示例代码(JDK 1.8)
public class HeapOOM {
public static void main(String[] args) {
List<Object> list = new ArrayList<>();
while (true) {
list.add(new OOMObject());
}
} static class OOMObject {
}
}
  • VM 参数
# 设置堆空间大小为 20M
-Xms20m -Xmx20m
-XX:+HeapDumpOnOutOfMemoryError
  • 异常信息
java.lang.OutOfMemoryError: Java heap space
Dumping heap to java_pid39807.hprof ...
Heap dump file created [27773554 bytes in 0.342 secs]
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
at java.util.Arrays.copyOf(Arrays.java:3210)
...

3.2 虚拟机栈和本地方法栈溢出

  • 示例代码(JDK 1.8)
public class StackOverflowError {
private int stackLength = 1; private void stackLeak() {
stackLength++;
stackLeak();
} public static void main(String[] args) {
JvmStackOverflow sof = new JvmStackOverflow();
try {
sof.stackLeak();
} catch (Throwable ex) {
// 注意这里是 Throwable,而非 Exception (Error 不是 Exception)
System.out.println("stack length: " + sof.stackLength);
throw ex;
}
}
}
  • VM参数

由于 HotSpot 虚拟机不区分 Java 虚拟机栈和本地方法栈。因此 -Xoss 参数(设置本地方法栈大小)并没有作用,栈空间只能由 -Xss 参数。

# Java 虚拟机栈大小
-Xss160K
  • 异常信息
stack length: 772
Exception in thread "main" java.lang.StackOverflowError
at com.jaxer.example.JvmStackOverflow.stackLeak(JvmStackOverflow.java:11)
at com.jaxer.example.JvmStackOverflow.stackLeak(JvmStackOverflow.java:12)
...

3.3 方法区和运行时常量池溢出

3.3.1 字符串常量

  • 示例代码
public class RuntimeConstantPoolOOM {
static String baseStr = "string"; public static void main(String[] args) {
List<String> list = new ArrayList<>();
while (true) {
String s = baseStr + baseStr;
baseStr = s;
list.add(s.intern());
}
}
}

JDK 1.8 参数及异常:

  • VM 参数
# 最大堆空间为 10M,永久代为 10M (为便于观察,打印了启动命令和 GC 信息)
-Xmx10m -XX:PermSize=10m -XX:MaxPermSize=10m
-XX:+PrintGCDetails -XX:+PrintCommandLineFlags
  • 异常信息
Java HotSpot(TM) 64-Bit Server VM warning: ignoring option PermSize=10m; support was removed in 8.0
Java HotSpot(TM) 64-Bit Server VM warning: ignoring option MaxPermSize=10m; support was removed in 8.0
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
at java.util.Arrays.copyOf(Arrays.java:3332)
...

JDK 1.7 参数及异常信息:

  • VM 参数
# 设置永久代大小为 10M
-XX:PermSize=10m -XX:MaxPermSize=10m
  • 异常信息
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
at java.util.Arrays.copyOf(Arrays.java:2367)
...

参考链接:https://www.cnblogs.com/paddix/p/5309550.html

3.3.2 类型信息

  • 示例代码
package com.jaxer.example.cglib;

public class OOMObject {
}

使用 CGLib 生成代码:

public class PermGenOOM {
public static void main(String[] args) {
try {
while (true) {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(OOMObject.class);
enhancer.setUseCache(false);
enhancer.setCallback(new MethodInterceptor() {
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
return methodProxy.invoke(o, objects);
}
});
enhancer.create();
}
} catch (Throwable t) {
t.printStackTrace();
}
}
}

JDK 1.8 参数及异常:

  • VM 参数
# 设置元空间大小为 10M
-XX:MetaspaceSize=10m -XX:MaxMetaspaceSize=10m
  • 异常信息
java.lang.OutOfMemoryError: Metaspace
at java.lang.Class.forName0(Native Method)
at java.lang.Class.forName(Class.java:348)
...

JDK 1.7 参数及异常信息:

  • VM 参数
# 设置永久代大小为 10M
-XX:PermSize=10m -XX:MaxPermSize=10m -XX:+PrintGCDetails
  • 异常信息
Exception in thread "main"
Exception: java.lang.OutOfMemoryError thrown from the UncaughtExceptionHandler in thread "main"

此处的异常无法被捕获,Debug 模式断点如下:

可以看到,这里实际还是永久代(PermGen space)OOM 异常。

3.4 本机直接内存溢出

  • 示例代码(JDK 1.8)
public class DirectMemoryOOM {
private static final int _1M = 2014 * 1024; public static void main(String[] args) {
List<ByteBuffer> list = new ArrayList<>();
while (true) {
ByteBuffer buffer = ByteBuffer.allocateDirect(_1M); // java.lang.OutOfMemoryError: Direct buffer memory
// ByteBuffer buffer = ByteBuffer.allocate(_1M); // java.lang.OutOfMemoryError: Java heap space
list.add(buffer);
}
}
}
  • VM 参数
# 设置堆内存最大为 20M,直接内存最大为 10M
-Xmx20m -XX:MaxDirectMemorySize=10m
  • 异常信息
java.lang.OutOfMemoryError: Direct buffer memory

4. 小结

本文主要分析了《Java 虚拟机规范》中规定的 Java 虚拟机管理的运行时内存区域,并以 HotSpot 虚拟机为例,分析了 JDK 1.7 和 1.8 内存溢出的情况。主要内容总结如下图:

PS: 一些虚拟机参数如下

# 设置堆空间大小
-Xms20m -Xmx20m # 设置虚拟机栈空间大小
-Xss160K # 设置永久代大小
-XX:PermSize=10m -XX:MaxPermSize=10m # 设置元空间大小
-XX:MetaspaceSize=10m -XX:MaxMetaspaceSize=10m # 打印 GC 日志
-XX:+PrintGCDetails # 打印命令行参数
-XX:+PrintCommandLineFlags # 堆栈信息
-XX:+HeapDumpOnOutOfMemoryError

JVM笔记-运行时内存区域划分的更多相关文章

  1. 理解JVM之JAVA运行时内存区域

    java运行时内存区域划分为方法区,堆区,虚拟机栈区,本地方法栈,程序计数器.其中方法区跟堆区是线程共享的数据区,其他的是线程私有的数据区. 1.程序计数器 程序计数器(PC)是一块较小的内存,他是存 ...

  2. JVM发展史和Java运行时内存区域

    目前三大主流JVM: Sun HotSpot:Sun于1997年收购Longview Technologies公司所得.Sun于2009年被Oracle收购. BEA JRockit:BEA于2002 ...

  3. JVM学习-运行时数据区域

    目录 前言 运行时数据区 程序计数器 Java虚拟机栈 局部变量表 基础数据类型 对象引用 returnAddress 操作数栈 动态链接 方法返回地址 Java堆 方法区 类型信息 字段描述符 方法 ...

  4. JVM<一>----------运行时数据区域

    参考:1.JVM Specification: http://docs.oracle.com/javase/specs/jvms/se7/html/jvms-2.html#jvms-2.5 2.< ...

  5. JVM 运行时数据区域划分

    目录 前言 什么是JVM JRE/JDK/JVM是什么关系 JVM执行程序的过程 JVM的生命周期 JVM垃圾回收 JVM的内存区域划分 一.运行时数据区包括哪几部分? 二.运行时数据区的每部分到底存 ...

  6. 1.JVM中的五大内存区域划分详解及快速扫盲

    本博客参考<深入理解Java虚拟机>这本书 视频及电子书详见:https://shimo.im/docs/HP6qqHx38xCJwcv9/ 一.快速扫盲 1. JVM是什么   JVM是 ...

  7. java架构之路-(JVM优化与原理)JVM的运行时内存模型

    还是我们上次的图,我们上次大概讲解了类加载子系统的执行过程,验证,准备,解析,初始化四个过程.还有我们的双亲委派机制. 我们这次来说一下运行时内存模型.上一段小代码. public class Mai ...

  8. Java运行时数据区域划分

    Java虚拟机在执行Java程序的过程中会把它所管理的内存划分为若干个不同的数据区域.这些区域都有各自的用途,以及创建和销毁时间.根据<Java虚拟机规范(Java SE 7版>的规定,J ...

  9. JVM运行时内存区域

    JVM运行java程序时会将内存划分为若干个不同的数据区域: (1)程序计数器: 1.占用内存空间不大. 2.程序计数器相当于JVM所执行的字节码(jvm指令)的“行号指示器”,通过程序计数器的“值” ...

随机推荐

  1. cs231n spring 2017 lecture2 Image Classification

    1. 相比于传统的人工提取特征(边.角等),深度学习是一种Data-Driven Approach.深度学习有统一的框架,喂不同的数据集,可以训练识别不同的物体.而人工提取特征的方式很脆弱,换一个物体 ...

  2. fare|gave it away|catch a glimpse |involve|rip|eternalstiff|

    N-COUNT 旅费;路费;车费A fare is the money that you pay for a journey that you make, for example, in a bus, ...

  3. 《内蒙古自治区第十二届大学生程序设计竞赛试题_D: 正品的概率》

    问题 D: 正品的概率 内存限制:128 MB时间限制:1 S标准输入输出 题目类型:传统评测方式:文本比较上传者:外部导入 提交:36通过:7 返回比赛提交提交记录 题目描述 袋中有m枚正品硬币,n ...

  4. python js正则表达式

    一.定义正则表达式 /.../  用于定义正则表达式 /.../g 表示全局匹配 /.../i 表示不区分大小写 /.../m 表示多行匹配JS正则匹配时本身就是支持多行,此处多行匹配只是影响正则表达 ...

  5. Java Random函数

    Java中存在着两种Random函数: 1.java.lang.Math.Random: 调用这个Math.Random()函数能够返回带正号的double值,该值大于等于0.0且小于1.0,即取值范 ...

  6. 那些被刻意“阉割”的名人名言

    "天才是百分之一的灵感,百分之九十九的汗水",这句名言大家都知道的吧!不过还有好多人不知道的是这句名言还有后半句:"但百分之一的灵感甚至比百分之九十九的汗水更重要.&qu ...

  7. MySQL远程访问失败的解决办法

    SQL连接预备知识:转载自https://jingyan.baidu.com/article/3ea51489e6cfbe52e61bba25.html问题:我想在另一个电脑通过navicat登陆本机 ...

  8. C#利用反射调用PB编译的COM组件

    问题: 1.根据COM组件的ProgID,得到COM组件公开的类型 2.创建COM组件提供的类型的对象 3.调用执行方法 正确姿势 C#利用反射调用(后期绑定)PB编译的COM组件 C#调用COM组件 ...

  9. MyBatis 判断条件为等于的时候,常量需要加 .toString()

    当MyBatis 判断条件为等于的时候,常量需要加 .toString() 来转换,这种方法是稳定的,推荐使用,比如: <!-- 正确的,稳定,推荐使用 --> <if test=& ...

  10. Microsoft Translator:消除面对面交流的语言障碍

    ​ Translator:消除面对面交流的语言障碍" title="Microsoft Translator:消除面对面交流的语言障碍"> ​ James Simm ...