程序小白在写代码的过程中,经常会不经意间写出发生内存溢出异常的代码。很多时候这类异常如何产生的都傻傻弄不清楚,如果能故意写出让jvm发生内存溢出的代码,有时候看来也并非一件容易的事。最近通过学习《深入理解java虚拟机-JVM高级特性与最佳实践》这本书,终于初步了解了一下java虚拟机的内存模型。本文通过写出使jvm发生内存溢出异常的代码来对自己的学习结果进行总结,同时也提醒自己以后写代码时候不要再跳进这个坑啦。
java的内存管理是由java虚拟机自动进行管理的,并不需要程序员过多的手动干预,这也就导致了初学java的人在不了解java内存模型的情况下也能愉快的进行coding。不过一旦涉及了内存泄露或者内存溢出以及垃圾回收(GC)方面的问题,如果不了解虚拟机是怎么管理内存的,那么排查问题,定位错误地点就显得无从下手了。首先上图看一下java虚拟机运行时数据区是什么样子(图片来源于网络):
从上图我们可以获得以下信息:
jvm内存分区可以分为所有线程共享数据区以及线程隔离数据区两部分。这也就是说图中的方法区和堆是由所有的线程共同使用的,而虚拟机栈、本地方法栈、程序计数器则是每个线程独有各自的响应数据区,各线程之间是互不干扰的。
jvm运行时数据区按照功能可分为方法区,堆,虚拟机栈,本地方法栈,程序计数器五个部分。各个部分存储的数据类型不同。
本文主要讲述如何让JVM发生内存溢出异常,有关JVM内存模型将会在另一篇文章中详细讲解,这里简单介绍各分区的作用:
堆:各线程共享;存放java对象实例,以及为数组分配的空间也在此处
虚拟机栈:线程私有;生命周期与线程相同,描述java方法执行的内存模型,每个方法在执行时创建栈帧,栈帧存储局部变量表、操作数栈、动态链接、返回地址(方法出口)等信息
本地方法栈:和虚拟机栈功能类似,线程私有;为虚拟机使用的Native方法提供服务
方法区:各线程共享;存储已被虚拟机加载的类的信息、final声明的常量,static修饰的静态变量,以及编译器编译后的java代码等数据(jdk7以后把常量池移到了堆中)
程序计数器:线程私有;可以看做是当前线程所执行程序的字节码的行号指示器
在jvm规范中,除了程序计数器内存区域没有规定任何内存溢出异常情形外,其他四个内存区域都会有相应的内存溢出异常发生的可能,所以jvm内存溢出异常发生在不同的内存区域具有不同的异常发生原因,知道一内存异常产生的位置,对于定位错误地点就很有指向性了。
下面就通过实例来展示,如何通过代码指定让不同的内存区域发生内存溢出异常。
一、java堆发生内存溢出:
java堆是用来存储对象实例以及数组的,使java堆发生内存溢出的要旨是:
不断创建对象
保证对象存活,不会被垃圾收集器回收
java虚拟机的内存大小是可以人为设置的,通过设置限制内存大小,可以很方便的实现内存溢出,节约了时间。
设置java堆内存大小的虚拟机参数为:-Xms堆的初始大小 -Xmx堆可扩展的最大值
延伸阅读
学习是年轻人改变自己的最好方式