JVM运行时内存区域划分
csdh11 2025-01-10 12:43 27 浏览
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 中永久代剩余的部分(主要是类型信息)移到了元空间,如下(虚线框表示已移除):
从上面几张图可以看出永久代和元空间的主要区别有以下两点:
- 存储位置不同
- 永久代是 JVM 内存的一部分,元空间在本地内存中(JVM 内存之外);
- 永久代使用不当可能导致 OOM,元空间一般不会。
- 存储内容不同:元空间存储的是「类型信息」(即类的元信息),而永久代除了类型信息,还包括「字符串常量池」和「静态变量」等(可以理解为元空间是永久代拆分出来的一部分)。
那么问题来了:为什么要把永久代替换为元空间呢?
原因大概有以下几点:
- Oracle 收购了两种 JVM:HotSpot VM 和 JRockit VM,并且想要将它们整合,但二者方法区实现差异较大;
- 字符串存在永久代中,容易出现性能问题和 OOM;
- 类及方法的信息大小较难确定,永久代大小难以确定:太小易导致永久代溢出,太大则易导致老年代溢出(JVM 内存是有限的,此消彼长);
- 永久代会为垃圾回收带来不必要的复杂度,且回收效率较低("性价比"低)。
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
相关推荐
- SpringBoot+LayUI后台管理系统开发脚手架
-
源码获取方式:关注,转发之后私信回复【源码】即可免费获取到!项目简介本项目本着避免重复造轮子的原则,建立一套快速开发JavaWEB项目(springboot-mini),能满足大部分后台管理系统基础开...
- Spring Boot+Vue全栈开发实战,中文版高清PDF资源
-
SpringBoot+Vue全栈开发实战,中文高清PDF资源,需要的可以私我:)SpringBoot致力于简化开发配置并为企业级开发提供一系列非业务性功能,而Vue则采用数据驱动视图的方式将程序...
- 2021年超详细的java学习路线总结—纯干货分享
-
本文整理了java开发的学习路线和相关的学习资源,非常适合零基础入门java的同学,希望大家在学习的时候,能够节省时间。纯干货,良心推荐!第一阶段:Java基础...
- 探秘Spring Cache:让Java应用飞起来的秘密武器
-
探秘SpringCache:让Java应用飞起来的秘密武器在当今快节奏的软件开发环境中,性能优化显得尤为重要。SpringCache作为Spring框架的一部分,为我们提供了强大的缓存管理能力,让...
- 3,从零开始搭建SSHM开发框架(集成Spring MVC)
-
目录本专题博客已共享在(这个可能会更新的稍微一些)https://code.csdn.net/yangwei19680827/maven_sshm_blog...
- Spring Boot中如何使用缓存?超简单
-
SpringBoot中的缓存可以减少从数据库重复获取数据或执行昂贵计算的需要,从而显著提高应用程序的性能。SpringBoot提供了与各种缓存提供程序的集成,您可以在应用程序中轻松配置和使用缓...
- 我敢保证,全网没有再比这更详细的Java知识点总结了,送你啊
-
接下来你看到的将是全网最详细的Java知识点总结,全文分为三大部分:Java基础、Java框架、Java+云数据小编将为大家仔细讲解每大部分里面的详细知识点,别眨眼,从小白到大佬、零基础到精通,你绝...
- 1,从零开始搭建SSHM开发框架(环境准备)
-
目录本专题博客已共享在https://code.csdn.net/yangwei19680827/maven_sshm_blog1,从零开始搭建SSHM开发框架(环境准备)...
- 做一个适合二次开发的低代码平台,把程序员从curd中解脱出来-1
-
干程序员也有好长时间了,大多数时间都是在做curd。现在想做一个通用的curd平台直接将我们解放出来;把核心放在业务处理中。用过代码生成器,在数据表设计好之后使用它就可以生成需要的controller...
- 设计一个高性能Java Web框架(java做网站的框架)
-
设计一个高性能JavaWeb框架在当今互联网高速发展的时代,构建高性能的JavaWeb框架对于提升用户体验至关重要。本文将从多个角度探讨如何设计这样一个框架,让我们一起进入这段充满挑战和乐趣的旅程...
- 【推荐】强&牛!一款开源免费的功能强大的代码生成器系统!
-
今天,给大家推荐一个代码生成器系统项目,这个项目目前收获了5.3KStar,个人觉得不错,值得拿出来和大家分享下。这是我目前见过最好的代码生成器系统项目。功能完整,代码结构清晰。...
- Java面试题及答案总结(2025版持续更新)
-
大家好,我是Java面试分享最近很多小伙伴在忙着找工作,给大家整理了一份非常全面的Java面试场景题及答案。...
- Java开发网站架构演变过程-从单体应用到微服务架构详解
-
Java开发网站架构演变过程,到目前为止,大致分为5个阶段,分别为单体架构、集群架构、分布式架构、SOA架构和微服务架构。下面玄武老师来给大家详细介绍下这5种架构模式的发展背景、各自优缺点以及涉及到的...
- 本地缓存GuavaCache(一)(guava本地缓存原理)
-
在并发量、吞吐量越来越大的情况下往往是离不开缓存的,使用缓存能减轻数据库的压力,临时存储数据。根据不同的场景选择不同的缓存,分布式缓存有Redis,Memcached、Tair、EVCache、Aer...
- 一周热门
- 最近发表
- 标签列表
-
- mydisktest_v298 (34)
- document.appendchild (35)
- 头像打包下载 (61)
- acmecadconverter_8.52绿色版 (39)
- word文档批量处理大师破解版 (36)
- server2016安装密钥 (33)
- mysql 昨天的日期 (37)
- parsevideo (33)
- 个人网站源码 (37)
- centos7.4下载 (33)
- mysql 查询今天的数据 (34)
- intouch2014r2sp1永久授权 (36)
- 先锋影音源资2019 (35)
- jdk1.8.0_191下载 (33)
- axure9注册码 (33)
- pts/1 (33)
- spire.pdf 破解版 (35)
- shiro jwt (35)
- sklearn中文手册pdf (35)
- itextsharp使用手册 (33)
- 凯立德2012夏季版懒人包 (34)
- 冒险岛代码查询器 (34)
- 128*128png图片 (34)
- jdk1.8.0_131下载 (34)
- dos 删除目录下所有子目录及文件 (36)