jvm中除了程序计数器,其余的区域都有可能会发生内存溢出java
当程序须要申请内存的时候,因为没有足够的内存,此时就会抛出OutOfMemoryError,这就是内存溢出linux
内存泄漏是因为使用不当,把一部份内存“丢掉了”,致使这部份内存不可用。
当在堆中建立了对象,后来没有使用这个对象了,又没有把整个对象的相关引用设为null。此时垃圾收集器会认为这个对象是须要的,就不会清理这部份内存。这就会致使这部份内存不可用。
因此内存泄漏会致使可用的内存减小,进而会致使内存溢出。算法
下面为了说明溢出的情景,会执行一些实例代码,同时须要给jvm指定参数服务器
-XX:MaxPermSize=size 永生代最大容量jvm
堆是存放对象的地方,那么只要在堆中疯狂的建立对象,那么堆就会发生内存溢出。工具
下面作一个堆溢出的实验
执行这段代码的时候,要给jvm指定参数优化
//jvm参数:-Xms20m -Xmx20m public class HeapOOMTest { public static void main(String[] args){ LinkedList<HeapOOMTest> l=new LinkedList<HeapOOMTest>();//做为GC Root while(true){ l.add(new HeapOOMTest());//疯狂建立对象 } } }
-Xms20m -Xmx20m做用是将jvm的最小堆容量和最大堆容量都设定为20m,这样就不会动态扩展jvm堆了
这段代码疯狂的建立对象,虽然对象没有声明变量名引用,可是将对象添加到队列l中,这样队列l就持有了一份对象的引用
经过可达性算法(jvm判断对象是否可被收集的算法)分析,队列l做为GC Root,每个对象都是l的一个可达的节点,因此疯狂建立的对象不会被收集,这就是内存泄漏,这样总有一天堆就溢出了。spa
运行结果:代理
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space at java.util.LinkedList.linkLast(Unknown Source) at java.util.LinkedList.add(Unknown Source) at test.HeapOOMTest.main(HeapOOMTest.java:23)
程序发生内存溢出,并提示发生在Java heap spacecode
用visualVM工具分析堆快照
若是发生内存泄漏:
step1:找出泄漏的对象
step2:找到泄漏对象的GC Root
step3:根据泄漏对象和GC Root找到致使内存泄漏的代码
step4:想法设法解除泄漏对象与GCRoot的链接
若是不存在泄漏:
优化程序,减少对象的生命周期
当发生堆溢出的时候,可让程序在崩溃时产生一份堆内存快照
产生堆内存快照的方法:
给jvm加上参数XX:+HeapDumpOnOutofMemoryError,这样就会在程序崩溃的时候,产生一份堆内存快照
分析堆内存快照我建议用jdk自带的可视化监视工具visualVM,位置在jdk安装目录下的bin,若是是在linux环境的话,能够把快照传到window。由于分析工具会占用很大的内存,不建议在服务端进行分析。
下面对刚才程序产生的堆内存快照进行分析。
打开visualVM,装入刚刚生成的快照,打开类标签页
队列和疯狂建立的对象几乎占满了整个栈,想要让垃圾收集器回收这些对象,要让他们与GC Root断开链接
双击HeapOOMTest类,跳转到实例标签页,能够查看这个类的全部实例
在实例上右键——显示最近的垃圾回收根节点,能够看到这个对象与根节点的链接
只要断开HeapOOMTest对象与LinkedList的链接,这些疯狂建立的对象就会被收集了
调用方法的时候,会在栈中入栈一个栈帧,若是当前栈的容量不足,就会发生栈溢出StackOverFlowError
那么只要疯狂的调用方法,而且有意的不让栈帧出栈就能够致使栈溢出了。
下面来一次栈溢出
//jvm参数:-Xss128k public class StackSOFTest { public void stackLeak(){ stackLeak();//递归,疯狂的入栈,有意不让出栈 } public static void main(String[] args){ StackSOFTest s=new StackSOFTest(); s.stackLeak(); } }
jvm设置参数-Xss128k,目的是缩小栈的空间,这样栈溢出“来的快一点”
程序中用了递归,让栈帧疯狂的入栈,又不让栈帧出栈,这样就会栈溢出了。
运行结果:
Exception in thread "main" java.lang.StackOverflowError at test.StackSOFTest.stackLeak(StackSOFTest.java:17) at test.StackSOFTest.stackLeak(StackSOFTest.java:17)
这里储存的是一些常量、字面量。若是运行时常量池内存不足,就会发生内存溢出。从jdk1.7开始,运行时常量池移动到了堆中,因此若是堆的内存不足,也会致使运行时常量池内存溢出。
下面来一次运行时常量池溢出,环境是jdk8
只要建立足够多的常量,就会发生溢出
/** * jvm参数: * jdk6之前:-XX:PermSize=10M -XX:MaxPermSize=10M * jdk7开始:-Xms10m -Xmx10m * */ public class RuntimePoolOOM { public static void main(String[] args){ int i=1; LinkedList<String> l=new LinkedList<String>();//保持常量的引用,防止被fullgc收集 while(true){ l.add(String.valueOf(i++).intern());//将常量添加到常量池 } } }
由于jdk6之前,运行时常量池是在方法区(永生代)中的,因此要限制永生代的容量,让内存溢出来的更快。
从jdk7开始,运行时常量池是在堆中的,那么固定堆的容量就行了
这里用了链表去保存常量的引用,是由于防止被fullgc清理,由于fullgc会清理掉方法区和老年代
intern()方法是将常量添加到常量池中去,这样运行时常量池一直都在增加,而后内存溢出
运行结果:
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space at java.lang.Integer.toString(Unknown Source) at java.lang.String.valueOf(Unknown Source) at test.RuntimePoolOOM.main(RuntimePoolOOM.java:30)
提示在heap区域发生内存溢出,果真运行时常量池被移到了堆中
方法区是存放类的信息,并且很难被gc,只要加载了大量类,就有可能引发方法区溢出
这里将不作演示了,想试试的能够用cglib建立大量的代理类
工做中也有可能会赶上方法区溢出:
当多个项目都有相同jar包的时候,又都存放在WEB-INF\lib\下,这样每一个项目都会加载一遍jar包。会致使方法区中有大量相同类(被不一样的类加载器所加载),又不会被gc掉。
若是实在不能瘦身类的话,那能够扩大方法区的容量,给jvm指定参数-XX:MaxPermSize=xxxM
查看原文:http://blog.zswlib.com/2016/11/07/jvm%e5%86%85%e5%ad%98%e6%ba%a2%e5%87%ba%e5%88%86%e6%9e%90/