上一篇文章我们讲了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. 【BZOJ5503】[GXOI/GZOI2019]宝牌一大堆(动态规划)

    [BZOJ5503][GXOI/GZOI2019]宝牌一大堆(动态规划) 题面 BZOJ 洛谷 题解 首先特殊牌型直接特判. 然后剩下的部分可以直接\(dp\),直接把所有可以存的全部带进去大力\(d ...

  2. [luogu3380][bzoj3196]【模板】二逼平衡树【树套树】

    题目地址 [洛谷传送门] 题目大意 区间查询k的排名,查找k排名的数,单点修改,区间前驱,区间后继. 感想 真的第一次写树套树,整个人都不对了.重构代码2次,发现样例都过不了,splay直接爆炸,可能 ...

  3. pip install升级包

    只需要python -m pip install --user --upgrade pip==9.0.3 只需要加一个--user

  4. 微服务之路由网关—zuul

    Zuul 简介Zuul 是 Netflix 公司开发的一个开源 APIGateway,其本质上是一个 WebServlet 应用.Zuul 的核心是一系列的 Filter. 为什么要使用 Zuul微服 ...

  5. 老男孩Python全栈学习 S9 日常作业 007

    1.把列表中所有姓周的人的信息删掉 lst = ['周老二', '周星星', '麻花藤', '周扒皮'] lst = ['周老二', '周星星', '麻花藤', '周扒皮'] lst2 = [] fo ...

  6. Kubernetes之StatefulSet

    什么是StatefulSet StatefulSet 是Kubernetes中的一种控制器,他解决的什么问题呢?我们知道Deployment是对应用做了一个简化设置,Deployment认为一个应用的 ...

  7. ZooKeeper 概述

    ZooKeeper:分布式应用程序的分布式协调服务 ZooKeeper:是用于分布式应用程序的分布式开源协调服务 可以简单理解:ZooKeeper = 文件系统 + 通知机制 从设计模式角度理解:Zo ...

  8. [再寄小读者之数学篇](2014-04-18 from 352558840@qq.com [南开大学 2014 年高等代数考研试题]反对称矩阵的组合)

    (2014-04-18 from 352558840@qq.com [南开大学 2014 年高等代数考研试题]反对称矩阵的组合) 设 ${\bf A},{\bf B}$ 都是反对称矩阵, 且 ${\b ...

  9. [物理学与PDEs]第2章第1节 理想流体力学方程组 1.2 理想流体力学方程组

    1.  质量守恒定律: 连续性方程 $$\bee\label{2_1_2_zl} \cfrac{\p\rho}{\p t}+\Div(\rho{\bf u})=0.  \eee$$ 2.  动量守恒定 ...

  10. C# mvc 前端调用 redis 缓存的信息

    新手 这几天网上学习下redis ,自己总结下过程,怕之后忘记了,基本会用最简单的,有的还是不懂,先记下来,自己摸索的. 没有安装redis的先安装,教程:http://www.cnblogs.com ...