Java虚拟机的内存区域与内存溢出异常

最近在读讲Java虚拟机(深入理解Java虚拟机)的书,果然是另一片天地。

做了实验,现在总结一下Java VM的内存区域划分和内存的溢出异常。

内存区域划分

  • 程序计数器
  • 虚拟机栈
  • 本地方法栈
  • 方法区
    • 运行时常量池(方法区的一部分,用于存放各种字面量和符号引用)
  • 直接内存

空间分配

以32位Win为例,每个进程可以分到最多2G的内存,要分给Xmx(最大堆容量),还有MaxPermSize(最大方法区容量),计数器消耗很小,还有VM进程本身消耗的内存,剩下的内存就是VM栈和本地方法栈了。

内存溢出的异常

听说QQ有一Java面试题就是问你如何把Java虚拟机搞挂。好吧,其实很容易,其实挂掉也分好多的异常。

Java堆异常

看看下面这代码:

可以加一些VM的运行参数:

参数限定了VM运行的堆的大小为20M,当Out of memory时,会生成Heap的Dump文件 ,方便我们调试。我们可以用Eclipse Memory Analyzer分析。

死循环运行的结果可想而知,VM抛出了内存异常,像这样,

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

Java默认的Hotspot VM中不区分虚拟机栈和本地方法栈,我们可以用-Xss参数设定栈大小(-Xoss本来是设置本地方法栈的大小,但实际上无效)。这里可能有两种异常,

  • 如果栈深度大于VM所允许的最大深度,就抛StackOverflowError。
  • 如果VM扩展栈时无法申请足够的空间,就抛OutOfMemoryError。(但此异常在实际实验中,无法抛出。)

用函数递归就可以把VM搞挂了。

会抛出StackOverflowError的异常,

我们可以调整本地变量的数量和用-Xss调整栈空间,来看对stackLength的影响。

变量表越多,-Xss越小,stackLength越浅,反之越深。

运行时常量池溢出

使用Sting.intern向运行时常量池中加内容。因为运行时常量池是方法区的一部分,所以可以用-XX:PermSize和-XX:MaxPermSize限制方法区的大小,间接控制常量池的大小。

VM参数用-XX:PermSize=2M -XX:MaxPerSize=2M来限制方法区的最大容量,注意不能太小,不然VM是不能启动的。

出错信息如下:

PermGen是永久带(Permanent Generation)的意思,只在HotSpot里才有这个概念。

方法区溢出

让我们来把方法区搞挂,方法就是动态产生大量的类。

VM参数:-XX:PermSize=5M -XX:MaxPermSize=5M

执行,好吧,它挂了!

如大量产生JSP网页时,就有可能出现方法区溢出。

本机直接内存溢出

DirectMemory可以通过-XX: MaxDirectMemorySize来指定,如不指定,则默认与Java堆的最大值相同。通过Unsafe实例对内存进行直接分配(因为Unsafe被设计成只有rt.jar中的类才能使用,所以要用反射的方法强行改变它的访问权限)。

VM参数用-XX:MaxDirectMemorySize=10M (No space between XX: and M),程序就挂了。

NOTE:这段代码在Eclipse下面默认是不会编译通过的。要改一个默认选项。Preferences-〉Java-〉Compiler-〉Errors/Warnings-〉Deprecated and restricted API-〉Forbidden reference (access rules): 改成Warning

好了,现在能挂的都挂了! 😎