遇到性能问题大家都很头疼,那么遇到性能问题,如何进行分析,有没有一个通用的分析思路呢,接下来,根据个人工作中的经验,做了三种常见OOM示例,以期提供一套通用的分析思路作为参考,希望对大家有帮助。

当然,同行大牛发现文中见解有问题的话,也希望能够予以指正。

一.堆溢出

    java堆是用来存放对象实例的,如果我们不断的创建对象,且保证GC Roots到对象之间可达,那么就可以避免垃圾回收机制清理这些创建的对象,因此,当对象数量到达堆的最大容量限制后就会产生内存溢出异常。下面我们来模拟这种异常。
(1)配置jvm启动参数,run as –> run configration 里可以配置。

下面几个参数的意思是:-Xms5m -Xmx5m :堆的最大最小值都是5m,避免自动扩展。-XX:+HeapDumpOnOutOfMemoryError :让虚拟机在出现堆溢出时自动dump当前内存堆转储快照,以便进行分析。-XX:+PrintGCDetails -XX:+PrintGCTimeStamps 打印gc信息便于我们分析。

Jvm OOM性能问题分析实践

(2)准备示例代码:

public class MemoryleaksDemo {

public static void main(String[] args) throws InterruptedException {

MemoryleaksDemo mem =new MemoryleaksDemo();
mem.BadExample();

}

public void BadExample() throws InterruptedException {
//内存泄露:o对象一直被v引用,gc无法清理掉,造成泄露
Vector v=new Vector(10);
while(true)
{
    Object o=new Object();
    v.add(o);
    o=null;  
}
}
}

(3)运行结果:java.lang.OutOfMemoryError: Java heap space,且自动生成了dump文件java_pid5043.hprof,在工程根目录下。

Jvm OOM性能问题分析实践

分析
分析dump文件

(1)打开dump文件,看到Overview中total使用了3.4M,其中一块内存占用了3.1M,点击Leak Suspects

Jvm OOM性能问题分析实践

(2)可以看到第一个问题,存在一个超大的java.lang.Object[]对象,点击Details

Jvm OOM性能问题分析实践

(3)可以看到有163840个Object对象,正是我们示例代码中新建的大量的object对象。

Jvm OOM性能问题分析实践

(4)为了验证我们的猜想,切换到Histogram 视图,按照Retained Heap由大到小排序,可以看到java.lang.Object 对象的数目很多,而且占用的空间也较大。

Jvm OOM性能问题分析实践

5)右键选择 Merge Shortest Paths to gc Roots->exclude all phantom/weak/soft etc.references ,查看该类的GC root(去除虚引用&软引用)

Jvm OOM性能问题分析实践

(6)可以看到有14条引用路径,其中第一个占用的内存最大,我们展开折叠项,发现了大量java.lang.Object 对象的引用,验证了上面的推测。

Jvm OOM性能问题分析实践

二.线程栈溢出
  设置线程栈的jvm参数是-Xss,该参数规定了每个线程堆栈的大小。JDK5.0以后每个线程堆栈大小为1M,以前每个线程堆栈大小为256K。根据应用的线程所需内存大小进行调整。在相同物理内存下,减小这个值能生成更多的线程。但是操作系统对一 个进程内的线程数还是有限制的,不能无限生成,经验值在3000~5000左右。
  栈(JVM Stack)存放的主要是栈帧( 局部变量表, 操作数栈 , 动态链接 , 方法出口信息 ),栈包含栈帧。如果抛出java.lang.StackOverflowError错误,一般出现此情况是因为方法运行时,请求新建栈帧,但是栈所剩空间小于战帧所需空间。例如,通过递归调用方法,不停的产生栈帧,一直把栈空间堆满,直到抛出异常。下面我们使用递归调用模拟栈溢出。
(1)配置jvm启动参数,run as –> run configration 里可以配置。
设置-Xss1m,堆5m,且在OOM时生产dump文件。

Jvm OOM性能问题分析实践

(2)栈溢出代码示例:
public class StackOverflow {
public void stackOverFlowMethod(){  
        stackOverFlowMethod();  
    }  
    /** 
     * 通过递归调用方法,不停的产生栈帧,一直把栈空间堆满,直到抛出异常
     */  
    public static void main(String[] args) {  
    StackOverflow sof = new StackOverflow();  
        sof.stackOverFlowMethod();  
    }  
}
(3)运行结果:

Exception in thread "main" java.lang.StackOverflowError。注意栈溢出并没有生成dump文件,虽然我们设置了

-XX:+HeapDumpOnOutOfMemoryError 参数。

Jvm OOM性能问题分析实践

三.方法区溢出
  方法区是所有线程共享的,主要用于存储类信息、常量池、方法数据、代码等。方法区逻辑上属于堆的一部分,但是为了与堆进行区分,通常又叫“非堆”。 
  从JDK1.8开始,永久代(PermGen)的概念被废弃掉了,取而代之的是Metaspace。Metaspace使用的是本地内存,也就是说在默认情况下Metaspace的大小只与本地内存大小有关。对于该区域的测试,基本思路是运行时产生大量的类去填满方法区,直到溢出。
  cglib原理:动态生成一个要代理类的子类,子类重写目标代理类的所有非final方法,在子类中拦截所有父类方法的调用,顺势织入横切逻辑。
(1)配置jvm参数
-Xms512m -Xmx512m -XX:MaxMetaspaceSize=1000m -XX:+HeapDumpOnOutOfMemoryError -XX:+PrintGCDetails -XX:+PrintGCTimeStamps

-XX:MaxMetaspaceSize=1000m这个参数用于限制Metaspace增长的上限,防止因为某些情况导致Metaspace无限的使用本地内存,影响到其他程序。当然metaspace还有其他相关参数,但是本例子不再过多阐述。

Jvm OOM性能问题分析实践

(2)准备示例代码
import java.lang.reflect.Method;
import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;


public class JavaMethodAreaOOM {
public static void main(String[] args) {
// TODO Auto-generated method stub
while(true) {
           //创建加强器
Enhancer enhancer = new Enhancer(); 
//为加强器指定要代理的业务类
        enhancer.setSuperclass(OOMObject.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 OOMObject{
}

}
(3)运行结果:

java.lang.OutOfMemoryError: Metaspace

Jvm OOM性能问题分析实践

分析
(1)通过jvisualvm工具监控,可以很清晰的看到metaspace空间一直在增长,最后达到1000m,即我们设置的最大值,装载类的数量也持续增加,从1万多增加到12万多。

Jvm OOM性能问题分析实践

(2)查看报错信息是在at org.springframework.cglib.core.AbstractClassGenerator.create处发生异常。
(3)打开dump文件的Histogram视图,

搜索org.springframework.cglib.core.AbstractClassGenerator,

发现相关的类org.springframework.cglib.core.AbstractClassGenerator$Source有3个对象。

Jvm OOM性能问题分析实践

(4)选中该class,右键List objects->with incoming references

Jvm OOM性能问题分析实践

(5)发现这个对象持有大量classloader 对象的引用。

例如<classloader>bestpay.com.cn.Testutil.JavaMethodAreaOOM$OOMObject$$EnhancerByCGLIB$$16725992_210,后面$$16725992_210都不一样,由此猜测这些class非同一代理类生成,这样就能理解为什么metaspace会增加,因为他们的class不同。

Jvm OOM性能问题分析实践Jvm OOM性能问题分析实践

四.常量池溢出

      在jdk1.8后,将常量池放到了堆中,所以对于常量池的溢出不会再报java.lang.OutOfMemoryError: Metaspace。

     class文件信息和动态常量池存储在方法区,class信息包括类信息和静态常量池。动态常量池里的内容是可以动态添加的,例如调用String的intern方法就能将string的值添加到String常量池中,8种基本数据类型(除了double和float)都使用了常量池。

    下面我们以String的intern方法来模拟常量池溢出。
(1)jvm参数设置为:
-Xms500m -Xmx500m -XX:MaxMetaspaceSize=20m -XX:+HeapDumpOnOutOfMemoryError -XX:+PrintGCDetails -XX:+PrintGCTimeStamps

-XX:MaxMetaspaceSize=20m Metaspace最大设置20m。

Jvm OOM性能问题分析实践

(2)准备示例代码
import java.util.ArrayList;
import java.util.List;

public class StringOomTest {

public   void test() {

List<String> list=new ArrayList<String>();
int i=0;
String str1=null;
while(true) {
i=i+5;
list.add(String.valueOf(i).intern());

}
}

public static void main(String [] args){

StringOomTest  str=new StringOomTest();
str.test();
    
}
}
(3)运行结果
java.lang.OutOfMemoryError: GC overhead limit exceeded。

发生该错误,是程序基本上耗尽了所有的可用内存,GC也清理不了,于是jvm发出了这样的信号: 执行垃圾收集的时间比例太大,有效的运算量太小。默认情况下,如果GC花费的时间超过98%, 并且GC回收的内存少于2%, JVM就会抛出这个错误。

Jvm OOM性能问题分析实践Jvm OOM性能问题分析实践

分析
通过jvisualvm工具实时监控

(1)可以看到已使用的metaspace大小从2.7m增加到8.7m左右后就不再增加了,并没有一直增加到设置的上限20m,也证实了生成的string常量不在metaspace空间中存在,而是在堆中存放。

Jvm OOM性能问题分析实践

(2)堆的大小从327m增加到479m后基本趋于稳定,不再增加,基本接近最大值500m,GC活动频繁。

Jvm OOM性能问题分析实践

(3)打开dump文件,点击Leak Suspects

Jvm OOM性能问题分析实践

(4)可以看到这个java.lang.Object[]数组占的空间特别大。

Jvm OOM性能问题分析实践

拉到最后,发现有7,877,381个java.lang.String对象。

Jvm OOM性能问题分析实践

(5)打开dominator视图,发现main thread占了99.74%的内存

Jvm OOM性能问题分析实践

展开java.lang.Thread,看到java.util.ArrayList对象,继续展开,看到object数组,下面有7,877,381个string对象,与overview中一致,正是示例代码新建的string对象。

Jvm OOM性能问题分析实践

(6)展开string对象,可以看到里面是一个char数组,存放的是int类型的数字。

Jvm OOM性能问题分析实践



本次分享就到这里,下次见。

作者简介:
隶属于甜橙金融质量平台性能团队,资深性能工程师,专注于性能领域,为性能问题的发现、分析、解决提供解决方案,以提高系统性能为己任。







相关文章: