JVM笔记-运行时内存区域划分

栏目: IT技术 · 发布时间: 4年前

1. 概述

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

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

JVM笔记-运行时内存区域划分

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

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 的运行时数据区示意图如下:

JVM笔记-运行时内存区域划分

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

JVM笔记-运行时内存区域划分

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

JVM笔记-运行时内存区域划分

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

  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 模式断点如下:

JVM笔记-运行时内存区域划分

可以看到,这里实际还是永久代(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 内存溢出的情况。主要内容总结如下图:

JVM笔记-运行时内存区域划分

PS: 一些虚拟机参数

# 设置堆空间大小

-Xms20m -Xmx20m


# 设置虚拟机栈空间大小

-Xss160K


# 设置永久代大小

-XX:PermSize=10m -XX:MaxPermSize=10m


# 设置元空间大小

-XX:MetaspaceSize=10m -XX:MaxMetaspaceSize=10m


# 打印 GC 日志

-XX:+PrintGCDetails


# 打印命令行参数

-XX:+PrintCommandLineFlags


# 堆栈信息

-XX:+HeapDumpOnOutOfMemoryError

JVM笔记-运行时内存区域划分


以上所述就是小编给大家介绍的《JVM笔记-运行时内存区域划分》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Mobilizing Web Sites

Mobilizing Web Sites

Layon, Kristofer / 2011-12 / 266.00元

Everyone has been talking about the mobile web in recent years, and more of us are browsing the web on smartphones and similar devices than ever before. But most of what we are viewing has not yet bee......一起来看看 《Mobilizing Web Sites》 这本书的介绍吧!

CSS 压缩/解压工具
CSS 压缩/解压工具

在线压缩/解压 CSS 代码

RGB转16进制工具
RGB转16进制工具

RGB HEX 互转工具

随机密码生成器
随机密码生成器

多种字符组合密码