1.java堆溢出
看代码:
注意,java_opts要设置为:如果不懂java_opts,自己百度去。
-server -Xms20m -Xmx20m -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=D:/test/
-Xms 堆的最小内存,我们这儿设置为20M
-Xmx 堆的最大内存,我们这儿也设置为20M
-XX:+HeapDumpOnOutOfMemoryError 设置这个是为了当出现内存溢出时系统自动生成堆的快照,这个快照的主要作用就是为了分析内存溢出异常的原因
-XX:HeapDumpPath=D:/test/ 生成快照的地址,我的是在windows系统下的
/**
* Created by
* Date : 2018/7/24 9:53
* 堆内存溢出
*/
public class HeapMain {
static class HeapObject{
}
public static void main(String[] args){
List<HeapObject> list=new ArrayList<>();
while(true){
list.add(new HeapObject());
}
}
}
执行结果:
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 outofmemory.HeapMain.main(HeapMain.java:17)
这段代码很简单,就是不停的创建对象,当创建的对象超出所设置的内存时,则报堆溢出异常。我们可以看到有“Java heap space”这么一句说明,这个就是说明堆空间溢出。
当系统报这个异常时,我们如何分析代码,找出内存溢出的原因?“深入理解java虚拟机”这本书推荐的是java自带的工具VisualVm或者它自带的命令,但是我用了,感觉好难用,用的有点卡。命令就更不用说了,打起来很烦琐。我个人推荐使用MemoryAnalyzer这个工具,仅仅是个人推荐。这个工具你可以集成在Eclipse上使用,也可以独立使用。我用的是idea开发工具,所以我独立使用这个工具。
这个地址等各位小伙伴看到的时候也不知道能不能用,不能用就到celipse官网上下载去。不过这个工具对java有版本之分,我这个是用在windows上的64位的java7。
上述代码运行报异常后,在D:/test/ (这个是我自己设置的生成快照的地址)下找到快照文件,后缀名是hprof的文件。
然后使用下载的工具打开这个文件。
图中Total:15.9M,墨绿色最大的占了15.5M,我们点击墨绿色,然后点击左上角的排第三个图标,
我们发现排列在最上面的一行数据内存占比为97.37%,然后展开,发现全是HeapMain下的HeapObject,
回到工具的主页面,再次点击最大的墨绿色,在弹出的工具栏中,我们选择“Java Basics==>thread details”,
我们找到HeapMain.java的第17行,
while(true){
list.add(new HeapObject());
}
现在问题已经显而易见了,就是这儿创建了很多个HeapObject造成的内存溢出。要想改掉这个问题,要么增大设置的内存,要么优化代码,不要循环添加这么多对象。
2.虚拟机栈与本地方法栈溢出
这两个内存空间都有两个异常,一个是栈溢出异常,一个是内存溢出异常,内存溢出异常不知道怎么实现,“深入理解java虚拟机”这本书关于内存溢出异常的是用线程举的例子,但我个人感觉,这样产生的内存溢出和栈空间大小没有关系,反而是和创建线程所需要的内存有关。所以如果有知道的与栈空间大小有关系的造成内存溢出异常的例子,可以在评论区留下代码给我哈。
下面是栈溢出的例子:
/**
* Created by
* Date : 2018/7/24 13:54
* 虚拟栈与本地方法栈,栈溢出异常
*/
public class StackMain {
private int i=0;
public void test(){
i++;
test();
}
public static void main(String[] args){
StackMain sm=new StackMain();
try{
sm.test();
}catch (Throwable e){
System.out.println("栈深度:"+sm.i);
throw e;
}
}
}
执行结果:
栈深度:995
Exception in thread "main" java.lang.StackOverflowError
at outofmemory.StackMain.test(StackMain.java:11)
at outofmemory.StackMain.test(StackMain.java:12)
at outofmemory.StackMain.test(StackMain.java:12)
at outofmemory.StackMain.test(StackMain.java:12)
at outofmemory.StackMain.test(StackMain.java:12)
这儿就报了栈溢出异常,实现的方法也很简单,就是在方法中调用自己的方法。
栈溢出异常不需要工具的解析,看到程序抛出的异常就能知道错在哪了。
3.方法区溢出
通过前一章节,我们知道方法区主要存储的是关于类的信息。因为要想方法区溢出,我们就无限的创建类。我们通过cglib来创建,不过关于cglib的jar包,我试了好多版本,基本上都有冲突,我试的没有冲突的版本是cglig-3.2.5.jar与asm-5.2.jar,当然直接用maven就没有版本冲突的问题了,不过我本人比较懒,所以直接找了jar包来测试。
测试代码如下:
注意,java_opts要设置为:
-server -XX:PermSize=10M -XX:MaxPermSize=10M -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=D:/test/
-XX:PermSize 方法区内存,我们这儿设置为10M
-XX:MaxPermSize 方法区最大内存,我们这儿也设置为10M
/**
* Created by
* Date : 2018/7/24 14:34
* 方法区内存溢出
*/
public class MethodMain {
public static void main(String[] args){
while(true){
Enhancer enhancer=new Enhancer();
enhancer.setSuperclass(MethodObject.class);
enhancer.setUseCache(false);
enhancer.setCallback(new MethodInterceptor() {
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
return methodProxy.invokeSuper(o,objects);
}
});
enhancer.create();
}
}
static class MethodObject{
}
}
执行结果:
java.lang.OutOfMemoryError: PermGen space
Dumping heap to D:/test/\java_pid3088.hprof ...
Heap dump file created [3871825 bytes in 0.019 secs]
Exception in thread "main"
Exception: java.lang.OutOfMemoryError thrown from the UncaughtExceptionHandler in thread "main"
其中“PermGen space”表示方法区溢出。我们这儿也可以使用工具来分析方法区溢出的原因,和前面的使用方法一样,在此就不重复罗嗦了。
4.本机直接内存溢出
注意,java_opts要设置为:
-server -Xmx20M -XX:MaxDirectMemorySize=10M -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=D:/test/
-Xmx 堆的最大内存
-XX:MaxDirectMemorySize 本机最大直接内存,如果不指定此属性,则会以堆的最大内存为本机最大直接内存
/**
* Created by
* Date : 2018/7/25 15:03
* 本机直接内存溢出
*/
public class DirectMain {
public static void main(String[] args)throws Exception{
Field unsafeField=Unsafe.class.getDeclaredFields()[0];
unsafeField.setAccessible(true);
Unsafe unsafe=(Unsafe) unsafeField.get(null);
while(true){
unsafe.allocateMemory(1024*1024);
}
}
}
执行结果:
Exception in thread "main" java.lang.OutOfMemoryError
at sun.misc.Unsafe.allocateMemory(Native Method)
at outofmemory.DirectMain.main(DirectMain.java:18)
执行完溢出异常,我并没有看到生成堆溢出的快照。在错误信息中,我们也没看到“PermGen space”,“Heap space”等相似的信息。如果我们在程序中发现有此异常,并且在程序中直接或间接的使用了NIO,那我们就要检查一下是不是本机直接内存溢出。