在实验之前先温习一下java虚拟机内存分区和对象访问方式。首先java虚拟机的内存分为程序计数器PC、虚拟机栈、本地方法栈、堆、方法区、运行时常量池、直接内存七大部分,其中运行时常量池属于方法区的一部分,直接内存不属于虚拟机运行时数据区,是本机真实内存的一部分。各个部分的内存超出最大允许范围并且无法扩展时就会抛出内存溢出错误。通过学习对象的访问方式我们知道,声明并实例化一个对象时,会在虚拟机栈上分配一块内存保存指向对象的reference,在堆中保存对象的实例化数据,在方法区中保存对象类型数据。好了下面开始试验。
试验一:虚拟机栈和本地方法栈溢出
在HotSpot虚拟机中并不区分虚拟机栈和本地方法栈,对于HotSpot虚拟机-Xoss(设置本地方法栈大小)参数虽存在,但实际上无效,栈容量只由-Xss参数设定。在java虚拟机中当线程请求的栈深度大于虚拟机所允许的最大深度,将抛出StackOverflowError异常,如果虚拟机在扩展栈时无法申请足够的内存空间,则抛出OutOfMemoryError异常。
实验代码:
public class StackOverflowTest{ private static class VmStackSOF{ private int stackLength=1; public void stackLeak(){ stackLength++; stackLeak(); } } public static void main(String[] args){ VmStackSOF sof=new VmStackSOF(); try { sof.stackLeak(); } catch (Throwable e) { e.printStackTrace(); System.out.println("sof.stackLength="+sof.stackLength); } } }
编译后,执行命令:java -Xss1k -XX:+HeapDumpOnOutOfMemoryError StackOverflowTest
此处通过-Xss参数将虚拟栈大小设为1k,-XX:+HeapDumpOnOutOfMemoryError参数输出堆栈信息
执行命令后输出如下:
at StackOverflowTest$VmStackSOF.stackLeak(StackOverflowTest.java:84)
at StackOverflowTest$VmStackSOF.stackLeak(StackOverflowTest.java:84)
at StackOverflowTest$VmStackSOF.stackLeak(StackOverflowTest.java:84)
at StackOverflowTest$VmStackSOF.stackLeak(StackOverflowTest.java:84)
at StackOverflowTest$VmStackSOF.stackLeak(StackOverflowTest.java:84)
at StackOverflowTest$VmStackSOF.stackLeak(StackOverflowTest.java:84)
at StackOverflowTest$VmStackSOF.stackLeak(StackOverflowTest.java:84)
at StackOverflowTest$VmStackSOF.stackLeak(StackOverflowTest.java:84)
sof.stackLength=18727
实验二:java堆溢出
通过不停的创建对象并阻止GC垃圾回收的方式来制造java堆内存溢出
实验代码:
import java.util.List; import java.util.ArrayList; public class HeapOverflowTest{ public static void main(String[] args){ List<Object> list = new ArrayList<Object>(); while(true){ list.add(new Object()); } } }
编译后执行命令:java -Xms10m -Xmx10m HeapOverflowTest
通过命令-Xms和-Xmx将虚拟机堆大小设置为10M,执行命令后输出如下:
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
at java.util.Arrays.copyOf(Arrays.java:3210)
at java.util.Arrays.copyOf(Arrays.java:3181)
at java.util.ArrayList.grow(ArrayList.java:261)
at java.util.ArrayList.ensureExplicitCapacity(ArrayList.java:235)
at java.util.ArrayList.ensureCapacityInternal(ArrayList.java:227)
at java.util.ArrayList.add(ArrayList.java:458)
at HeapOverflowTest.main(HeapOverflowTest.java:8)
实验三:运行时常量池溢出
本文通过不断向运行时常量池中添加内容的方法制造运行时常量池溢出
实验代码:
import java.util.List; import java.util.ArrayList; public class RuntimeConstantPoolOOM{ public static void main(String[] args){ List<String> list=new ArrayList<String>(); int i = 0; while(true){ list.add(String.valueOf(i++).intern()); } } }
注:在8.0以后的jdk已经移除了-XX:PermSize和-XX:MaxPermSize参数,本文采用-Xmx参数设置最大堆为10M。
编译后执行命令:java -Xmx10m RuntimeConstantPoolOOM
输出如下:
Exception in thread "main" java.lang.OutOfMemoryError: GC overhead limit exceeded
at java.lang.Integer.toString(Integer.java:401)
at java.lang.String.valueOf(String.java:3099)
at RuntimeConstantPoolOOM.main(RuntimeConstantPoolOOM.java:9)
可以看到GC overhead limit exceeded信息,此信息含义是虚拟机花了大部分时间做垃圾回收处理但实际回收的内存小的可怜,当然了,因为对象分配到了运行时常量池中且引用都没有失效,所以最终导致了内存溢出异常。
好啦,今天暂时写到这里,明天继续试验方法区溢出和本地内存溢出试验。晚安啦!