三,内存溢出异常
1.堆溢出:这个是最容易溢出的,实例化对象,无法获得空间存放。
设置jvm的堆空间大小:堆初始化大小20M,最大20M,新生代10M,打印打击回收详细信息,设置新生代中的比例为8:2
注意一定要设置-Xmx的堆最大值,不然它会扩大堆的大小,
测试代码:
public class MyT {
static class OOMObject{
}
public static void main(String []args) {
List<OOMObject> list=new ArrayList<>();
while(true) {
list.add(new OOMObject());
}
}
}
实验结果:显示发送oom,发生在heap space中。
(堆的内存有自动扩展功能)使用内存映像分析工具对堆的内存进行快照,然后提出出来分析,分析的目的是判断:堆中存货的对象是必须的还是垃圾。
如果是垃圾就是:内存遗漏,分析gc为什么不进行回收。
如果是必要的,则检查堆参数,看是否可以进行内存上调。或者代码的存储结构设计不合理,(比如疯狂递归等等)
2.栈溢出
包括虚拟机栈和本地方法栈溢出, -Xoss参数设置本地方法栈大小无效果,因为hotSpot不区分虚拟机栈和本地方法栈,栈容量由-Xss参数决定,
异常抛出:1.线程请求的深度>虚拟机运行的max深度。抛出StackOverflowError异常
2.栈动态扩展申请不了空间。hotSpot不支持扩展。比如说,定义了大量的本地变量(局部变量)存放如栈中。或者又是递归调用。 抛出OutOfMemoryError异常
参数设置:
测试代码: 第一种异常情况,栈深度不够
public class MyT {
private int stackLength =-1;
public void stackLeak() {
stackLength++;
stackLeak(); //递归调用
}
public static void main(String []args) {
MyT my=new MyT();
try {
my.stackLeak();
}catch(Throwable e) {
System.out.println("stack length: "+my.stackLength);
throw e;
}
}
}
结果:
测试代码:第二种情况,局部变量表太大,没有空间可以满足分配
当我们设置栈的大小为64k方便实验的时候,jvm提示:The stack size specified is too small, Specify at least 108k,最小也要保持108k,所以我们选择了108k测试
public class MyT {
private int stackLength =-1;
public void stackLeak() {
long u1=0,u2=0,u3=0,u4=0,u5=0,u6=0,u7=0,u9=0,u8=0,u11=0,u12=0,u13=0,u14=0,u15=0,u16=0,u17=0,u18=0,u19=0;
long u1z=0,u2z=0,u3z=0,u4z=0,u5z=0,uz6=0,u7z=0,u9z=0,uz8=0,u1z1=0,u1z2=0,u1z3=0,uz14=0,uz15=0,u1z6=0,u17z=0,u18z=0,u1z9=0;
long ua1=0,ua2=0,ua3=0,ua4=0,ua5=0,ua6=0,u7a=0,u9a=0,u8a=0,u11a=0,u12a=0,u13a=0,u14a=0,u1a5=0,u1a6=0,u1a7=0,u1a8=0,ua19=0;
long uq1=0,uq2=0,u3q=0,uq4=0,uq5=0,u6q=0,u7q=0,u9q=0,uq8=0,u1q1=0,u1q2=0,u1q3=0,u1q4=0,u1q5=0,u16q=0,u1q7=0,u1q8=0,u1q9=0;
long uw1=0,uw2=0,uw3=0,u4w=0,u5w=0,uw6=0,wu7=0,uw9=0,uw8=0,uw11=0,uw12=0,u1w3=0,uw14=0,uw15=0,u1w6=0,u1w7=0,u1w8=0,uw19=0;
stackLength++;
stackLeak(); //递归调用
}
public static void main(String []args) {
MyT my=new MyT();
try {
my.stackLeak();
}catch(Throwable e) {
System.out.println("stack length: "+my.stackLength);
throw e;
}
}
}
实验发现,不管是栈容量太小,还是单个栈帧太大,抛出的都是sof异常,
第三种情况,线程数量太多,导致unable to create native thread,此时就会抛出oom异常
解决该异常的方法:原理是操作系统分配给进程的内存空间有限,当这个空间 - 最大堆内存 - 最大方法区内存 - 直接内存 - 虚拟机进程自身消耗的内存,程序计数器消耗很小,忽略不计,剩下的内存 就交给 虚拟机栈和本地方法栈来分配,如果一个栈的容量越小,那么可以分配的线程数量就越多,所以当发生因为线程分配导致的oom的时候,如果不能减少线程数量,那么就可以通过减少堆内存,或者减少栈容量来换取更多的线程。(注意,栈是私有的,所以一个栈就对应一个线程,-Xss也就是线程栈的容量,而不是总容量)
3.方法区和运行时常量池溢出:由于jdk8使用元空间来代替永久代,这里就要讨论一下,两者的区别:
在jdk7后,常量池转移到了堆中,使用字符串的intern()可以想常量池添加内容,死循环添加就可以溢出。
设置堆大小,10M,注意最大值一定要设置
测试代码:
public class MyT {
public static void main(String []args) {
List<String> list=new ArrayList<>();
int i=0;
while(true) {
list.add(String.valueOf(i++).intern());
}
}
}
结果:
方法区的溢出则是,动态代理之类的,产生很多的类进行填满,spring等框架就经常使用动态代理,设置元空间大小:
设置参数:
public class MyT {
public static void main(String []args) {
while(true) {
Enhancer enhancer=new Enhancer();
enhancer.setSuperclass(myObject.class);
enhancer.setUseCache(false);
enhancer.setCallback(new MethodInterceptor() {
public Object intercept(Object obj,Method method,Object[] args,MethodProxy proxy)throws Throwable{
return proxy.invokeSuper(obj, args);
}
});
enhancer.create();
}
}
static class myObject{
}
}
4.本机 直接内存 溢出:它的溢出不会产生很明显的东西,判断自己是否使用了nio。而其他溢出异常有没有发生。
测试代码:
public static void main(String []args) {
Field unsafeField =Unsafe.class.getDeclaredFields()[0];
unsafeField.setAccessible(true);
Unsafe unsafe=(Unsafe)unsafeField.get(null);
while(true) {
unsafe.allocateMemory(1024*1024);
}
}
四,性能调优
1.堆 中间为m
-Xms:初始化堆大小
-Xmx:最大堆大小
-Xmn:年轻代大小
-XX:NewRatio=n :设置年轻代和老年代的比值
-XX:SurvivorRatio=n 年轻代中Eden区和两个Survivor区的比值,注意Survivor有两个.
2.栈 中间为s
-Xss 设置每个线程的栈大小
3.元数据区设置
-XX:MetaspaceSize 元数据区的初始化大小
-XX:MaxMetaspaceSize 元数据去的最大大小
4.异常设置
-XX:+HeapDumpOnOutOfMemoryError 使得jvm在产生内存溢出时,自动生成堆内存快照
-XX:HeapDumpPath 改变默认的堆内存快照生成路径
5.收集器设置
-XX:+UseSerialGC:设置串行收集器
-XX:+UseParallelGC:设置并行收集器
-XX:+UseParallelOldGC:设置并行老年代收集器
-XX:+UseConcMarkSweepGC:设置并发收集器
6.垃圾回收统计信息
-XX:+PrintGC
-XX:+PrintGCDetails
-XX:+PrintGCTimeStamps
-Xloggc:filename