程序小白在写代码的过程中,经常会不经意间写出发生内存溢出异常的代码。很多时候这类异常如何产生的都傻傻弄不清楚,如果能故意写出让jvm发生内存溢出的代码,有时候看来也并非一件容易的事。最近通过学习《深入理解java虚拟机-JVM高级特性与最佳实践》这本书,终于初步了解了一下java虚拟机的内存模型。本文通过写出使jvm发生内存溢出异常的代码来对自己的学习结果进行总结,同时也提醒自己以后写代码时候不要再跳进这个坑啦。

java的内存管理是由java虚拟机自动进行管理的,并不需要程序员过多的手动干预,这也就导致了初学java的人在不了解java内存模型的情况下也能愉快的进行coding。不过一旦涉及了内存泄露或者内存溢出以及垃圾回收(GC)方面的问题,如果不了解虚拟机是怎么管理内存的,那么排查问题,定位错误地点就显得无从下手了。首先上图看一下java虚拟机运行时数据区是什么样子(图片来源于网络):

从上图我们可以获得以下信息:

  1. jvm内存分区可以分为所有线程共享数据区以及线程隔离数据区两部分。这也就是说图中的方法区和堆是由所有的线程共同使用的,而虚拟机栈、本地方法栈、程序计数器则是每个线程独有各自的响应数据区,各线程之间是互不干扰的。
  2. jvm运行时数据区按照功能可分为方法区,堆,虚拟机栈,本地方法栈,程序计数器五个部分。各个部分存储的数据类型不同。

本文主要讲述如何让JVM发生内存溢出异常,有关JVM内存模型将会在另一篇文章中详细讲解,这里简单介绍各分区的作用:

  1. 堆:各线程共享;存放java对象实例,以及为数组分配的空间也在此处
  2. 虚拟机栈:线程私有;生命周期与线程相同,描述java方法执行的内存模型,每个方法在执行时创建栈帧,栈帧存储局部变量表、操作数栈、动态链接、返回地址(方法出口)等信息
  3. 本地方法栈:和虚拟机栈功能类似,线程私有;为虚拟机使用的Native方法提供服务
  4. 方法区:各线程共享;存储已被虚拟机加载的类的信息、final声明的常量,static修饰的静态变量,以及编译器编译后的java代码等数据(jdk7以后把常量池移到了堆中)
  5. 程序计数器:线程私有;可以看做是当前线程所执行程序的字节码的行号指示器

在jvm规范中,除了程序计数器内存区域没有规定任何内存溢出异常情形外,其他四个内存区域都会有相应的内存溢出异常发生的可能,所以jvm内存溢出异常发生在不同的内存区域具有不同的异常发生原因,知道一内存异常产生的位置,对于定位错误地点就很有指向性了。

下面就通过实例来展示,如何通过代码指定让不同的内存区域发生内存溢出异常。

一、java堆发生内存溢出:

java堆是用来存储对象实例以及数组的,使java堆发生内存溢出的要旨是:

  • 不断创建对象
  • 保证对象存活,不会被垃圾收集器回收

java虚拟机的内存大小是可以人为设置的,通过设置限制内存大小,可以很方便的实现内存溢出,节约了时间。

设置java堆内存大小的虚拟机参数为:-Xms堆的初始大小 -Xmx堆可扩展的最大值

 1 package Text.JVM;
2
3 import java.util.ArrayList;
4 import java.util.List;
5
6 /**
7 * JVM堆中存储java对象实例和数组
8 * VM Args: -Xms20m -Xmx20m (限制堆的大小不可扩展)
9 * @author Administrator
10 *
11 */
12 public class HeapOutOfMemoryError {
13
14 public static class OOMObject {
15 }
16 public static void main(String[] args) {
17 List<Object> list=new ArrayList<>();
18 // 不断创建对象,并保证GC Roots到对象之间有可达路径,避免垃圾回收清除创建的对象
19 while (true) {
20 list.add(new OOMObject());
21 System.out.println(System.currentTimeMillis());
22 }
23 }
24
25 }

jvm虚拟机启动参数设置:

运行结果:

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 Text.JVM.HeapOutOfMemoryError.main(HeapOutOfMemoryError.java:20)

注意运行结果第一行末尾:java.lang.OutOfMemoryError: Java heap space 。java heap space明确的指出了异常发生的区域:堆。

二、虚拟机栈发生内存溢出异常:

能够使虚拟机栈发生内存溢出异常的情形有两种:

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

使java虚拟机栈发生内存溢出异常的要旨:

  • 对应情形1,使用不合理的递归
  • 对应情形2,不断创建活跃的线程

使用递归导致虚拟机栈内存溢出异常的示例:

 1 package Text.JVM;
2
3
4 /*
5 * 线程请求的栈深度超过虚拟机所允许的最大深度,将抛出StackOverflowError异常
6 * 最常见引起此类异常的情形时使用不合理的递归调用
7 * VM Args:-Xss256k
8 *
9 * @author Administrator
10 *
11 */
12 public class StackOverflowError {
13
14 // 记录内存溢出时的栈深度
15 private int stackLength = 1;
16
17 // 递归调用的方法
18 public void stackLeak() {
19 stackLength++;
20 stackLeak();
21 }
22
23 public static void main(String[] args) {
24
25 StackOverflowError oomError = new StackOverflowError();
26 try {
27 oomError.stackLeak();
28 } catch (Throwable e) {
29 System.out.println("栈深度为:" + oomError.stackLength);
30 throw e;
31 }
32 }
33
34 }

程序运行结果:

栈深度为:2491
Exception in thread "main" java.lang.StackOverflowError
at Text.JVM.StackOverflowError.stackLeak(StackOverflowError.java:19)
at Text.JVM.StackOverflowError.stackLeak(StackOverflowError.java:20)
at Text.JVM.StackOverflowError.stackLeak(StackOverflowError.java:20)
at Text.JVM.StackOverflowError.stackLeak(StackOverflowError.java:20)
  ......
异常信息中的:java.lang.StackOverflowError表明了内存溢出区域为虚拟机栈。

通过创建线程导致虚拟机栈内存溢出异常的示例:

 1 package Text.JVM;
2
3 /*
4 * 通过不断创建活跃线程,消耗虚拟机栈资源
5 * VM Args:-Xss256k
6 */
7 public class StackOutOfMemoryError {
8
9 // 线程任务,每个线程任务一直在运行
10 private void wontStop() {
11 while (true) {
12 System.out.println(System.currentTimeMillis());
13 }
14 }
15
16 // 不断地创建线程
17 public void stackLeadByThread() {
18 while (true) {
19 Thread thread = new Thread(new Runnable() {
20
21 @Override
22 public void run() {
23 wontStop();
24 }
25 });
26 thread.start();
27 }
28 }
29
30 public static void main(String[] args) {
31 StackOutOfMemoryError oomError=new StackOutOfMemoryError();
32 oomError.stackLeadByThread();
33 }
34
35 }

理论上本段代码的运行结果应该是: Exception in thread "main" java.lang.OutOfMemoryError: unable to create new native thread ,但是由于windows平台虚拟机java线程映射到操作系统的内核线程上,执行上述代码有较大风险可能会导致操作系统假死(我运行完是真的死了),所以运行需谨慎!!!(写了这么长的博客有一半没保存,重启后丢了这我会乱说???都是泪啊)。

三、方法区和运行时常量池内存溢出异常

方法区的作用是存储 Java 类的结构信息,当我们创建对象实例后,对象的类型信息存储在方法区之中,实例数据存放在堆中;实例数据指的是在 Java 中创建的各种实例对象以及它们的值,类型信息指的是定义在 Java 代码中的常量、静态变量、以及在类中声明的各种方法、方法字段等等;同时可能包括即时编译器编译后产生的代码数据。通过在运行时产生大量的类,或者工程本身具有大量的类,而方法区分配的空间不足以容纳如此多的类信息的时候就会产生方法区内存溢出异常。

运行时常量池是方法取得一部分,程序中使用到的String类型字面量以及基本数据类型的一部分数据会存储在常量池中。我们使用String.intern()方法来测试,是运行时常量池发生内存溢出。此方法的作用是:如果字符串常量池中不包含一个等于此String对象的字符串,则将此对象包含的字符串添加到常量池中,并返回此对象的引用。

使方法区发生内存溢出的要旨:

  • 程序运行时动态创建的大量类,导致方法区内存空间不足
  • 程序中存有大量字面量等数据导致常量区内存不足

常量池内存溢出示例代码(仅在jdk6之前的版本中有效):

1 package Text.JVM;
2
3 import java.util.ArrayList;
4 import java.util.List;
5
6 /*
7 * VM Args: -XX:PermSize=10M -XX:MaxPermSize=10M(限制常量池容量)
8 *
9 */
10 public class RunTimeConstantPoolOutOfMemoryError {
11
12 public static void main(String[] args) {
13 // 使用list保持常量池引用,避免常量池内的数据被垃圾回收清除
14 List<String> list = new ArrayList<>();
15 long i = 0;
16 while (true) {
17 String string = (i++) + "";
18 list.add(string.intern());
19 }
20 }
21
22 }

据说此段代码在jdk6之前的版本中运行时会产生:Exception in thread "main" java.lang.OutOfMemoryError: PermGen space 其中PermGen space指示内存溢出发生在运行时常量池中。

但是,我在jdk7的环境中运行得到的结果却是: Exception in thread "main" java.lang.OutOfMemoryError: Java heap space  指示内存溢出发生在堆中而不是方法区中的常量池!!!都说实践是检验真理的唯一标准,果真是没错的。因为在 JDK1.2 ~ JDK6 的实现中,HotSpot 使用永久代实现方法区,而从 JDK7 开始 Oracle HotSpot 开始移除永久代,JDK7中符号表被移动到 Native Heap中,字符串常量和类引用被移动到 Java Heap中。在 JDK8 中,永久代已完全被元空间(Meatspace)所取代。关于常量池的存放位置还有待进一步研究,不过上段代码是可以引起常量池的内存溢出的。

通过运行时动态产生大量的类产生方法区内存溢出示例这里就不提供了,书中提供了使用CGLib使方法区出现内存异常的示例:

 1 /**
2 * VM Args: -XX:PermSize=10M -XX:MaxPermSize=10M
3 * @author zzm
4 */
5 public class JavaMethodAreaOOM {
6
7 public static void main(String[] args) {
8 while (true) {
9 Enhancer enhancer = new Enhancer();
10 enhancer.setSuperclass(OOMObject.class);
11 enhancer.setUseCache(false);
12 enhancer.setCallback(new MethodInterceptor() {
13 public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
14 return proxy.invokeSuper(obj, args);
15 }
16 });
17 enhancer.create();
18 }
19 }
20
21 static class OOMObject {
22
23 }
24 }

运行结果:

Caused by: java.lang.OutOfMemoryError: PermGen space
at java.lang.ClassLoader.defineClass1(Native Method)
at java.lang.ClassLoader.defineClassCond(ClassLoader.java:632)
at java.lang.ClassLoader.defineClass(ClassLoader.java:616)
... 8 more

我并没有运行过这段代码,因为我本人对于CGLib并不了解,这里只要知道发生内存溢出时显示:PermGen space那就是方法区内存溢出准没错啦。

四、直接内存不足导致内存溢出异常

直接内存不是虚拟机运行时的数据区的一部分,也不是在java虚拟机规范中定义的区域,但是这部分区域也被频繁使用,也会导致OutOfMemoryError

直接内存应用于NIO,直接内存区域默认为对内存的最大值,通过-XX:MaxDirectMemorySize可以显式的指定直接内存大小,如果忽略直接内存,容易使各个内存区域总和大于物理内存限制,从而导致动态扩展时出现内存溢出现象。

具体的代码示例也就不贴了,因为平时的学习过程中还没有使用过或者还没有意识到自己使用过直接内存区。

(注:以上内容完全是为了记录学习结果,所有内容皆为原创,如果觉得对您有用欢迎转载,但请注明出处,尊重原创,如果文内有内容不对的地方还请多多指教)

如何写出让java虚拟机发生内存溢出异常OutOfMemoryError的代码的更多相关文章

  1. 深入理解java虚拟机【内存溢出实例】

    通过简单的小例子程序,演示java虚拟机各部分内存溢出情况: (1).java堆溢出: Java堆用于存储实例对象,只要不断创建对象,并且保证GC Roots到对象之间有引用的可达,避免垃圾收集器回收 ...

  2. Java 虚拟机的内存溢出

    在Java虚拟机规范的描述中,除了程序计数器外,虚拟机内存的其他几个运行时区域都有发生 OutOfMemoryError 异常的可能. 在Eclipse中进行JVM参数设置 可以直接通过上方菜单栏的 ...

  3. java虚拟机(四)--内存溢出、内存泄漏、SOF

    学习了java运行时数据区,知道每个内存区域保存什么数据,可以参考:https://www.cnblogs.com/huigelaile/p/diamondshine.html,然后了 解内存溢出和内 ...

  4. 【java虚拟机】内存溢出解决思路

    转自:https://blog.csdn.net/u013521220/article/details/79523633 内存溢出与数据库锁表的问题,可以说是开发人员的噩梦,一般的程序异常,总是可以知 ...

  5. java虚拟机涉及内存溢出

    Java语言写的代码是.java文件,它会被特定程序编译(javac.exe,它会被Eclipse之类的IDE调用)成字节码(bytecode),字节码不能直接在CPU上运行,需要另一个程序读取并执行 ...

  6. 【java虚拟机】内存溢出与内存泄漏

    作者:平凡希 原文地址:https://www.cnblogs.com/xiaoxi/p/7354857.html 一.基本概念 内存溢出:简单地说内存溢出就是指程序运行过程中申请的内存大于系统能够提 ...

  7. (一)深入java虚拟机之内存溢出与分析

    一.内存溢出程序 public class Test { public static void main(String[] args) { List<User> userList=new ...

  8. c# 多线程里面创建byte数组发生内存溢出异常求解

    在多线程里面读取一个400多M的Xml文件,首先将其读入FileStream里面,然后,在执行 byte [] bts = new byte[fs.Length]; 这句代码时,出现内存溢出的异常,求 ...

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

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

随机推荐

  1. hibernate一对一主键单向关联

    关联是类(类的实例)之间的关系,表示有意义和值得关注的连接. 本系列将介绍Hibernate中主要的几种关联映射 Hibernate一对一主键单向关联Hibernate一对一主键双向关联Hiberna ...

  2. 【MSP是什么】MSP认证之成功的项目群管理

    同项目管理相比,项目群管理是为了实现项目群的战略目标与利益,而对一组项目进行的统一协调管理. 项目群管理 项目群管理是以项目管理为核心.单个项目上进行日常性的项目管理,项目群管理是对多个项目进行的总体 ...

  3. Atitit.每月数据采集与备份 v4

    Atitit.每月数据采集与备份 v4 备份检查表 r12 00cate 00item im Inputmethod  ok ok Log Log ok cyar Cyar log  ... ok c ...

  4. Atitit 多元化战略 适合我们发展 的核心业务attilax总结

    Atitit 多元化战略 适合我们发展 的核心业务attilax总结 1.1. 历史的大趋势,全球范围内人员的大流动1 1.2. 衣食住行1 1.3. 农村包围城市战略1 1.4. 挪开三座大山(住房 ...

  5. redis的面试题

    1:使用redis有哪些好处? (1) 速度快,因为数据存在内存中,类似于HashMap,HashMap的优势就是查找和操作的时间复杂度都是O(1) (2) 支持丰富数据类型,支持string,lis ...

  6. JS原生第三篇 (帅哥)

    1.1 数 组 1. 数组           看电影    电影院  座位 大的变量     里面可以放很多的值 var  arr = [1,3,57]; var ar = new Array(); ...

  7. 简单编写Makefile

    相信很多朋友都有过这样的经历,看着开源项目中好几页的makefile文件,不知所云.在日常学习和工作中,也有意无意的去回避makefile,能改就不写,能用ide就用ide.其实makefile并没有 ...

  8. 【完全开源】微信客户端.NET版

    目录 说明 功能 原理步骤 一些参考 说明 前两天比较闲,研究了一下web版微信.因为之前看过一篇博客讲微信web协议的,后来尝试分析了一下,半途中发现其实没什么意义,但又不想半途而废,所以最后做出了 ...

  9. 如果你想深刻理解ASP.NET Core请求处理管道,可以试着写一个自定义的Server

    我们在上面对ASP.NET Core默认提供的具有跨平台能力的KestrelServer进行了详细介绍(<聊聊ASP.NET Core默认提供的这个跨平台的服务器——KestrelServer& ...

  10. 从零开始编写自己的C#框架(23)——上传组件使用说明

    文章导航 1.前言 2.上传组件功能说明 3.数据库结构 4.上传配置管理 5.上传组件所使用到的类 6.上传组件调用方法 7.效果演示 8.小结 1.前言 本系列所使用的是上传组件是大神July开发 ...