Java虚拟机——Java内存区域与内存溢出
内存区域
Java虚拟机在执行Java程序的过程中会把他所管理的内存划分为若干个不同的数据区域。Java虚拟机规范将JVM所管理的内存分为以下几个运行时数据区:程序计数器、Java虚拟机栈、本地方法栈、Java堆、方法区。下面详细阐述各数据区所存储的数据类型。
(图片来自:http://blog.csdn.net/ns_code/article/details/17565503)
程序计数器(Program Counter Register)
一块较小的内存空间,它是当前线程所执行的字节码的行号指示器,字节码解释器工作时通过改变该计数器的值来选择下一条需要执行的字节码指令,分支、跳转、循环等基础功能都要依赖它来实现。每条线程都有一个独立的的程序计数器,各线程间的计数器互不影响,因此该区域是线程私有的。
当线程在执行一个Java方法时,该计数器记录的是正在执行的虚拟机字节码指令的地址,当线程在执行的是Native方法(调用本地操作系统方法)时,该计数器的值为空。另外,该内存区域是唯一一个在Java虚拟机规范中么有规定任何OOM(内存溢出:OutOfMemoryError)情况的区域。
Java虚拟机栈(Java Virtual Machine Stacks)
该区域也是线程私有的,它的生命周期也与线程相同。虚拟机栈描述的是Java方法执行的内存模型:每个方法被执行的时候都会同时创建一个栈帧,栈它是用于支持续虚拟机进行方法调用和方法执行的数据结构。对于执行引擎来讲,活动线程中,只有栈顶的栈帧是有效的,称为当前栈帧,这个栈帧所关联的方法称为当前方法,执行引擎所运行的所有字节码指令都只针对当前栈帧进行操作。栈帧用于存储局部变量表、操作数栈、动态链接、方法返回地址和一些额外的附加信息。在编译程序代码时,栈帧中需要多大的局部变量表、多深的操作数栈都已经完全确定了,并且写入了方法表的Code属性之中。因此,一个栈帧需要分配多少内存,不会受到程序运行期变量数据的影响,而仅仅取决于具体的虚拟机实现。
在Java虚拟机规范中,对这个区域规定了两种异常情况:
1、如果线程请求的栈深度大于虚拟机所允许的深度,将抛出StackOverflowError异常。
2、如果虚拟机在动态扩展栈时无法申请到足够的内存空间,则抛出OutOfMemoryError异常。
这里有一点要重点说明,在多线程情况下,给每个线程的栈分配的内存越大,越容易产生内存溢出异常。操作系统为每个进程分配的内存是有限制的,虚拟机提供了参数来控制Java堆和方法区这两部分内存的最大值,忽略掉程序计数器消耗的内存(很小),以及进程本身消耗的内存,剩下的内存便给了虚拟机栈和本地方法栈,每个线程分配到的栈容量越大,可以建立的线程数量自然就越少。因此,如果是建立过多的线程导致的内存溢出,在不能减少线程数的情况下,就只能通过减少最大堆和每个线程的栈容量来换取更多的线程。当由于创建过量线程发生OOM时,会报错:java.lang.OutOfMemoryError, unable to create new native thread。
本地方法栈(Native Method Stacks)
该区域与虚拟机栈所发挥的作用非常相似,只是虚拟机栈为虚拟机执行Java方法服务,而本地方法栈则为使用到的本地操作系统(Native)方法服务。与虚拟机栈一样,本地方法栈区域也会抛出StackOverflowError与OutOfMemoryError异常。
Java堆(Java Heap)
Java Heap是Java虚拟机所管理的内存中最大的一块,它是所有线程共享的一块内存区域,几乎所有的对象实例和数组都在这类分配内存。Java Heap是垃圾收集器管理的主要区域,因此很多时候也被称为“GC堆”。
根据Java虚拟机规范的规定,Java堆可以处在物理上不连续的内存空间中,只要逻辑上是连续的即可。如果在堆中没有内存可分配时,并且堆也无法扩展时,将会抛出OutOfMemoryError异常。
注意:随着JIT编译器的发展与逃逸技术逐渐成熟,所有对象都分配在堆上也逐渐变得不是那么绝对了,线程共享的Java堆中也可能划分出线程私有的分配缓冲区(TLAB)。
方法区(Method Area)
方法区也是各个线程共享的内存区域,它用于存储已经被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。Java虚拟机规范把方法区描述为Java堆的一个逻辑部分,而且它和Java Heap一样不需要连续的内存,可以选择固定大小或可扩展,另外,虚拟机规范允许该区域可以选择不实现垃圾回收。相对而言,垃圾收集行为在这个区域比较少出现。不过,这部分区域的回收是有必要的,如果这部分区域永远不回收,那么类型就无法卸载,我们就无法加载更多的类,HotSpot的该区域有实现垃圾回收。
根据Java虚拟机规范的规定,当方法区无法满足内存分配需求时,将抛出OutOfMemoryError异常。
直接内存(Direct Memory)
直接内存并不是虚拟机运行时数据区的一部分,也不是Java虚拟机规范中定义的内存区域,它直接从操作系统中分配,因此不受Java堆大小的限制,但是会受到本机总内存的大小及处理器寻址空间的限制,因此它也可能导致OutOfMemoryError异常出现。在JDK1.4中新引入了NIO机制,它是一种基于通道与缓冲区的新I/O方式,可以直接从操作系统中分配直接内存,即在堆外分配内存,这样能在一些场景中提高性能,因为避免了在Java堆和Native堆中来回复制数据。
当使用超过虚拟机允许的直接内存时,虚拟机会抛出OutOfMemoryError异常,由DirectMemory导致的内存溢出,一个明显的特征是在Heap Dump文件中不会看见明显的异常。一般来说,如果发现OOM后Dump文件很小,那就应该考虑一下,是不是这块内存发生了溢出。
内存溢出
Java堆内存溢出
public class HeapOOM {
static class OOMObject {
} public static void main(String[] args) {
List<OOMObject> list = new ArrayList<OOMObject>(); while (true) {
list.add(new OOMObject());
}
}
}
运行以上代码时,可以增加运行参数-Xms20m -Xmx20m,该参数限制Java堆大小为20M,不可扩展。运行结果如下:
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
at java.util.Arrays.copyOf(Arrays.java:2245)
at java.util.Arrays.copyOf(Arrays.java:2219)
at java.util.ArrayList.grow(ArrayList.java:242)
at java.util.ArrayList.ensureExplicitCapacity(ArrayList.java:216)
at java.util.ArrayList.ensureCapacityInternal(ArrayList.java:208)
at java.util.ArrayList.add(ArrayList.java:440)
at HeapOOM.main(HeapOOM.java:17)
可以看到,在堆内存溢出时,除了会报错java.lang.OutOfMemoryError外,还会跟着进一步提示Java heap space。
虚拟机栈和本地方法栈溢出
要让虚拟机栈内存溢出,我们可以使用递归调用:因为每次方法调用都需要向栈中压入调用信息,当栈的大小固定时,过深的递归将向栈中压入过量信息,导致StackOverflowError:
public class JavaVMStackSOF {
private int stackLength = 1; public void stackLeak() {
stackLength++;
stackLeak();
} public static void main(String[] args) throws Throwable {
JavaVMStackSOF oom = new JavaVMStackSOF();
try {
oom.stackLeak();
} catch (Throwable e) {
System.out.println("stack length:" + oom.stackLength);
throw e;
}
}
}
运行以上代码,输出如下:
stack length:10828
Exception in thread "main" java.lang.StackOverflowError
at JavaVMStackSOF.stackLeak(JavaVMStackSOF.java:10)
at JavaVMStackSOF.stackLeak(JavaVMStackSOF.java:11)
at JavaVMStackSOF.stackLeak(JavaVMStackSOF.java:11)
可以看到,在我的电脑上运行以上代码,最多支持的栈深度是10828层,当发生栈溢出时,会报错java.lang.StackOverflowError。
方法区溢出
方法区用于存放Class的相关信息,如类名、访问修饰符、字段描述等,对于这个区域的测试,基本思路是运行时使用CGLib产生大量的类去填充方法区,直到溢出:
public class JavaMethodAreaOOM {
static class OOMObject {
}
public static void main(String[] args) {
while( true) {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(OOMObject. class);
enhancer.setUseCache( false);
enhancer.setCallback( new MethodInterceptor() {
@Override
public Object intercept(Object obj, Method method, Object[] args,
MethodProxy proxy) throws Throwable {
return proxy.invokeSuper(obj, args);
}
});
enhancer.create();
}
}
}
运行时增加虚拟机参数:-XX:PermSize=10M -XX:MaxPermSize=10M,限制永久代大小为10M,最后报错为java.lang.OutOfMemoryError: PermGen space。报错信息明确说明,溢出区域为永久代。
总结
本文主要说明Java虚拟机一共分为哪几块内存区域,以及这几块内存区域是否会内存溢出,如果这些区域发生内存溢出报错如何。了解这些知识后,以后遇到内存溢出报错,我们就可以定位到具体内存区域,然后具体问题,具体分析。
参考
- 《深入理解Java虚拟机》
Java虚拟机——Java内存区域与内存溢出的更多相关文章
- 深入理解java虚拟机系列(一):java内存区域与内存溢出异常
文章主要是阅读<深入理解java虚拟机:JVM高级特性与最佳实践>第二章:Java内存区域与内存溢出异常 的一些笔记以及概括. 好了開始.假设有什么错误或者遗漏,欢迎指出. 一.概述 先上 ...
- 【深入Java虚拟机】之一:Java内存区域与内存溢出
转载请注明出处:http://blog.csdn.net/ns_code/article/details/17565503 内存区域 Java虚拟机在执行Java程序的过程中会把他所管理的内存划分为若 ...
- 《深入理解Java虚拟机》-----第2章 Java内存区域与内存溢出异常
2.1 概述 对于从事C.C++程序开发的开发人员来说,在内存管理领域,他们即是拥有最高权力的皇帝又是执行最基础工作的劳动人民——拥有每一个对象的“所有权”,又担负着每一个对象生命开始到终结的维护责任 ...
- 【深入Java虚拟机(1)】:Java内存区域与内存溢出
原文出处: 兰亭风雨 内存区域 Java虚拟机在执行Java程序的过程中会把他所管理的内存划分为若干个不同的数据区域.Java虚拟机规范将JVM所管理的内存分为以下几个运行时数据区:程序计数器.Jav ...
- 《深入理解java虚拟机》第二章 Java内存区域与内存溢出异常
第二章 Java内存区域与内存溢出异常 2.2 运行时数据区域
- 读书笔记,《深入理解java虚拟机》,第二章,java内存区域与内存溢出异常
第二节,运行时数据区域. 在这个章节中,作者给出了一个java虚拟机运行时数据区的框图,图的左侧是方法区和堆,这两个数据区是所有的线程所共享的.然后是虚拟机栈.本地方法栈.还有程序计数器,这三个 ...
- 深入了解Java虚拟机(1)java内存区域与内存溢出异常
java内存区域与内存溢出异常 一.运行时数据区域 1.程序计数器:线程私有,用于存储当前所执行的指令位置 2.Java虚拟机栈:线程私有,描叙Java方法执行模型:执行方法时都会创建一个栈帧,存储局 ...
- 《深入理解Java虚拟机》笔记--第二章、Java内存区域与内存溢出异常
Java程序员把内存的控制权交给了Java虚拟机.在Java虚拟机内存管理机制的帮助下,程序员不再需要为每一个new操作写对应的delete/free代码,而且不容易出现内存泄露和溢出. 虚拟机在执行 ...
- 深入理解java虚拟机---->java内存区域与内存溢出异常
2. java内存区域于内存溢出异常 2.1 概述: 对于C/C++而言,内存管理具有最高的权利,既拥有每一个对象的“所有权”,又担负着每一个对象生命开始到结束的维护责任. 对于java而言,则把内存 ...
- java虚拟机(一)-java内存区域与内存溢出异常
1.简述:java虚拟机在执行java程序的过程中,会把他所管理的内存分为以下几个区域, 1.1.程序计数器 1.2.虚拟机栈 1.3.本地方法栈 1.4.java堆 1.5.方法区 如图所示: 2. ...
随机推荐
- Linux学习-基于CentOS7的MariaDB数据库的主从复制
一.MySQL主从复制原理 主从同步过程中主服务器有一个工作线程I/O dump thread,从服务器有两个工作线程I/O thread和SQL thread: 主服务器: dump Thread: ...
- VM虚拟机中MAC OS调整磁盘大小
1.打开终端,输入diskutil list: 2.从显示的列表中找到你需要扩展的分区(是分区不是磁盘,分区的ID一般为diskXsX): 3.然后输入 diskutil resizeVolume d ...
- Windows系统安装教程
身边很多朋友电脑系统因为这样那样的问题不能开机,需要重装系统,但是又不知道从何下手.很不建议去电脑店重装系统,因为电脑店用的操作系统很多都有捆绑软件和主页捆绑,对电脑体验影响很坏.这篇教程只是针对新手 ...
- 20180715-Java日期时间
import java.util.Date;public class DateDemo{ public static void main(String[] args){ //初始化Date对象 Dat ...
- php面试专题---7、文件及目录处理考点
php面试专题---7.文件及目录处理考点 一.总结 一句话总结: 用脑子:基本文件操作和目录操作了解一波,不必强求 1.不断在文件hello.txt头部写入一行“Hello World”字符串,要求 ...
- .NETFramework:System.Net.WebClient.cs
ylbtech-.NETFramework:System.Net.WebClient.cs 提供用于将数据发送到和接收来自通过 URI 确认的资源数据的常用方法 1.返回顶部 1. #region 程 ...
- Ajax对数据的删除与查看
1.数据库找一张表: 颜色表2.主页面主页面的代码用到tbody:TBODY作用是:可以控制表格分行下载,从而提高下载速度.(网页的打开是先表格的的内容全部下载完毕后,才显示出来,分行下载可以先显示部 ...
- HTML--JS 多列求和
<html> <head> <title>多列求和</title> <script type="text/javascript" ...
- poj2718Smallest Difference (穷竭搜索)
escription - 题目描述 给定若干位十进制数,你可以通过选择一个非空子集并以某种顺序构建一个数.剩余元素可以用相同规则构建第二个数.除非构造的数恰好为0,否则不能以0打头. 举例来说,给定数 ...
- C#后台获取当前时间并格式化
最常用的:DateTime.Now.ToString("yyyy-MM-dd"); //今天DateTime.Now.Date.ToShortDateString(); //昨天, ...