上一篇文章我们讲了JVM运行时数据区域与内存溢出异常,其中对于内存溢出异常这部分将的不够详细,这篇文章将着重讲解Java内存溢出异常的相关知识。如果有没看过上一篇文章的小伙伴们,请点击Java内存区域与内存溢出异常

Java的内存溢出异常主要分为两类:分别是内存溢出和栈溢出。在以下几种情况,会抛出内存异常:Java堆溢出、虚拟机栈和本地方法栈溢出、方法区和运行时常量池溢出、以及本机直接内存溢出,下面讲一一介绍这几类异常。

Java堆溢出

在Java内存区域与内存溢出异常中讲过,Java堆主要是用来存储对象实例的。这部分的内存区域的大小可以通过-Xms参数和-Xmx参数进行设置,通常将-Xms和-Xmx的值设置为相同的值,以减少内存扩展或者收缩时的开销。

Java堆的空间是有限的,受到物理内存与虚拟机内存的双重限制(通常虚拟机内存的会设置成小于物理内存)。因此,如果对象实例的数量不断增加,而垃圾回收机制没有进行及时清理的时候,对象实例所占用的空间就会达到Java堆的空间最大值。此时,就会因为Java堆内存不足,导致无法为新的实例分配空间,从而抛出OutOfMemoryError异常。

通过设置-Xms20m -Xmx20m运行以下代码可以模拟这一情况:

/**
* VM Args: -Xms20m -Xmx20m
*
* @author bdq
*/
public class HeapOOM {
static class OOMObject { } public static void main(String[] args) {
List<OOMObject> objects = new ArrayList<>();
while (true) {
objects.add(new OOMObject());
}
}
}

  

运行结果:

java.lang.OutOfMemoryError: Java heap space

  

这即是常见的OOM异常,针对这类异常,往往在打印异常信息的同时会进一步提示异常原因,如上图所示的”Java heap space”。当然,只靠这点信息不足以判断到底是内存容量设置小了,还是出现了内存泄漏(关于内存泄漏的知识将会在后面的文章中进行讲述)。因此我们还要辅以其他手段来进一步确定问题的根源,比如加上-XX:+HeapDumpOnOutOfMemoryError参数使得虚拟机在出现内存溢出异常时Dump出当前的内存堆转储快照,然后用相关的工具进行分析。这类知识,本篇文章暂不作过多的讲解,将会在后面的文章一一介绍。

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

为什么要把虚拟机栈和本地方法栈的溢出放在一起讨论呢,因为在HotSpot虚拟机中并不区分虚拟机栈和本地方法栈。对于HotSpot来说,虽然说-Xoss参数是用来设置本地方法栈大小,但实际上是无效的,栈的容量只由-Xss参数设定。

在Java虚拟机规范中对虚拟机栈和本地方法栈描述了两种异常:

  1. 如果线程请求的栈深度大于虚拟机所允许的最大深度,将抛出StackOverflowError异常。
  2. 如果虚拟机在扩展栈时无法申请到足够的内存空间,则抛出OutOfMemoryError异常。

这种分类其实并不是很明确,因为内存太小或者已使用的栈空间太大都会导致栈空间无法继续分配。

StackOverflowError的出现条件很简单,下面这段简单的代码就会出现栈溢出:

public class JavaVMStackSOF {
private int stackLength = 1; public void stackLeak() {
stackLength++;
stackLeak();
} public static void main(String[] args) {
JavaVMStackSOF javaVMStackSOF = new JavaVMStackSOF();
try {
javaVMStackSOF.stackLeak();
} catch (Throwable e) {
System.out.println("Stack length:" + javaVMStackSOF.stackLength);
throw e;
}
}
}

  

运行结果如下:

Stack length:18663
Exception in thread "main" java.lang.StackOverflowError
at cn.bdqfork.jvm.JavaVMStackSOF.stackLeak(JavaVMStackSOF.java:12)
at cn.bdqfork.jvm.JavaVMStackSOF.stackLeak(JavaVMStackSOF.java:12)
at cn.bdqfork.jvm.JavaVMStackSOF.stackLeak(JavaVMStackSOF.java:12)
at cn.bdqfork.jvm.JavaVMStackSOF.stackLeak(JavaVMStackSOF.java:12)
......

  

对上面的运行结果,不同的计算机Stack length的大小是不确定的,从输出的异常信息来看,是因为stackLeak方法递归调用层数过多导致的。在大多数情况下,栈深度在虚拟机默认参数下是够用的。

OutOfMemoryError异常比较难以出现,一般发生在多线程环境下。当创建一个线程时,虚拟机会分配一个私有的栈空间给相应的线程,这个空间的大小可以用-Xss参数来设置。通过不断的创建新的进程,可以产生内存溢出异常。

原因是这样的,当进程运行时,操作系统分配给进程的内存是有限的,Java堆和方法区这两部分占了大部分,忽略到程序计数器所占用的很小的一块内存,不计算虚拟机本身占用的内存,剩下的就由虚拟机栈和本地方法栈所占用。因此,创建的线程数量到达一定程度时,虚拟机栈和本地方法栈所占用的空间就会使得进程的内存空间不够用,从而抛出内存溢出异常。

这部分的测试代码如下:

public class JavaVMStackOOM {
private void dontStop() {
while (true) { }
} public void stackLeakByThread() {
while (true) {
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
dontStop();
}
});
thread.start();
}
} public static void main(String[] args) {
JavaVMStackOOM javaVMStackOOM = new JavaVMStackOOM();
javaVMStackOOM.stackLeakByThread();
}
}

  

这段代码的运行有一定的风险,因为Java的线程并不是完全的用户级线程,有映射到操作系统的部分,所以可能会产生系统假死的现象,请谨慎运行。

运行结果如下:

Exception in thread "main" java.lang.OutOfMemoryError: unable to create new native thread

  

由此可以看出,我们在进行多线程开发时,对于线程的数量要有一定的把握,线程池的复用是很有必要的。

受限于篇幅原因,剩余的知识点,将会在下一篇进行讲解。

 

Java内存溢出异常(上)的更多相关文章

  1. Java内存溢出异常(下)

    此篇是上一篇文章Java内存溢出异常(上)的续篇,没有看过的同学,可以先看一下上篇.本篇文章将介绍剩余的两个溢出异常:方法区和运行时常量池溢出. 方法区和运行时常量池溢出 这部分为什么会放在一起呢?在 ...

  2. JVM(2) Java内存溢出异常

    在Java虚拟机运行时数据区中,除了程序计数器之外,虚拟机栈.本地方法栈.方法区和Java堆都有发生OutOfMemoryError(简称OOM)异常的可能. 一.Java堆溢出 Java堆用于存储对 ...

  3. java内存溢出异常

    名称 特征 作用 配置参数 异常 程序 计数器 占用内存小,线程私有, 生命周期与线程相同 大致为字节码行号指示器 无 无 虚拟机栈 线程私有,生命周期与线程 相同,使用连续的内存空间 Java 方法 ...

  4. 模拟Java内存溢出

    本文通过修改虚拟机启动参数,来剖析常见的java内存溢出异常(基于jdk1.8). 修改虚拟机启动参数Java堆溢出虚拟机栈溢出方法区溢出本机直接内存溢出 修改虚拟机启动参数   这里我们使用的是ID ...

  5. 如何写出让java虚拟机发生内存溢出异常OutOfMemoryError的代码

    程序小白在写代码的过程中,经常会不经意间写出发生内存溢出异常的代码.很多时候这类异常如何产生的都傻傻弄不清楚,如果能故意写出让jvm发生内存溢出的代码,有时候看来也并非一件容易的事.最近通过学习< ...

  6. 深入理解java虚拟机系列(一):java内存区域与内存溢出异常

    文章主要是阅读<深入理解java虚拟机:JVM高级特性与最佳实践>第二章:Java内存区域与内存溢出异常 的一些笔记以及概括. 好了開始.假设有什么错误或者遗漏,欢迎指出. 一.概述 先上 ...

  7. JVM自动内存管理-Java内存区域与内存溢出异常

    摘要: JVM内存的划分,导致内存溢出异常的可能区域. 1. JVM运行时内存区域 JVM在执行Java程序的过程中会把它所管理的内存划分为以下几个区域: 1.1 程序计数器 程序计数器是一块较小的内 ...

  8. Java虚拟机内存溢出异常--《深入理解Java虚拟机》学习笔记及个人理解(三)

    Java虚拟机内存溢出异常--<深入理解Java虚拟机>学习笔记及个人理解(三) 书上P39 1. 堆内存溢出 不断地创建对象, 而且保证创建的这些对象不会被回收即可(让GC Root可达 ...

  9. 《深入理解Java虚拟机》-----第2章 Java内存区域与内存溢出异常

    2.1 概述 对于从事C.C++程序开发的开发人员来说,在内存管理领域,他们即是拥有最高权力的皇帝又是执行最基础工作的劳动人民——拥有每一个对象的“所有权”,又担负着每一个对象生命开始到终结的维护责任 ...

随机推荐

  1. 关于访问Jira和Confluence服务越来越缓慢的解决办法阐述

    Jira和Confluence部署在同一台服务器上,跑一段时间后,发现访问jira和confluence时,打开越来越缓慢.这是因为根据主机物理内存不同,默认的java虚拟机内存也会不同(一个较低值) ...

  2. BZOJ2588 主席树 + 树上差分

    https://www.lydsy.com/JudgeOnline/problem.php?id=2588 题意:强制在线的询问树链权值第K小(无修) 这种类似于第K小的题,一般容易想到主席树,但是树 ...

  3. Vim使用技巧汇总

    一 写在开头 1.1 本文内容 Vim使用技巧与学习资源汇总. 二 Vim学习资源 1. Vimtutor 2. Vim中文帮助(http://vimcdoc.sourceforge.net/doc/ ...

  4. [物理学与PDEs]第5章习题5 超弹性材料中客观性假设的贮能函数表达

    设超弹性材料的贮能函数 $\hat W$ 满足 (4. 19) 式, 证明由它决定的 Cauchy 应力张量 ${\bf T}$ 满足各向同性假设 (4. 7) 式. 证明: 若贮能函数 $W$ 满足 ...

  5. python,可变对象,不可变对象,深拷贝,浅拷贝。

    学习整理,若有问题,欢迎指正. python 可变对象,不可变对象 可变对象 该对象所指定的内存地址上面的值可以被改变,变量被改变后,其所指向的内存地址上面的值,直接被改变,没有发生复制行为,也没有发 ...

  6. python下调用pytesseract识别某网站验证码

    一.pytesseract介绍 1.pytesseract说明 pytesseract最新版本0.1.6,网址:https://pypi.python.org/pypi/pytesseract Pyt ...

  7. 第29月第21天 ios android curl

    1.curl https://github.com/gcesarmza/curl-android-ios/ 2.json https://github.com/danielmapar/cpp-scra ...

  8. ST表学习笔记

    ST表是一种利用DP思想求解最值的倍增算法 ST表常用于解决RMQ问题,即求解区间最值问题 接下来以求最大值为例分步讲解一下ST表的建立过程: 1.定义 f[i][j]表示[i,i+2j-1]这个长度 ...

  9. 函数的if--while流程控制

    一.流程控制---if 1.if条件判断 age=18 hight=1.70 sex="female" is_beautiful=True if sex=="female ...

  10. 【全网最全的博客美化系列教程】08.自定义地址栏Logo

    全网最全的博客美化系列教程相关文章目录 [全网最全的博客美化系列教程]01.添加Github项目链接 [全网最全的博客美化系列教程]02.添加QQ交谈链接 [全网最全的博客美化系列教程]03.给博客添 ...