JVM调优<一>内存溢出及优化分析

153 阅读4分钟

一、调优的概述

1.1调优的目的

  • 防止出现OOM,进行JVM规划和预调优
  • 解决程序运行中各种OOM
  • 减少Full GC出现的频率,解决运行慢、卡顿问题

二、生产环境的问题

2.1、堆溢出

原因: 1、代码中可能存在大对象分配 2、可能存在内存泄漏,导致在多次GC之后,还是无法找到一块足够大的内存容纳当前对象。

解决方法: 1、检查是否存在大对象的分配,最有可能的是大数组分配 2、通过jmap命令,把堆内存dump下来,使用MAT等工具分析一下,检查是否存在内存泄漏的问题 3、如果没有找到明显的内存泄漏,使用-Xmx加大堆内存 4、还有一点容易被忽略,检查是否有大量的自定义的 Finalizable 对象,也有可能是框架内部提供的,考虑其存在的必要性

案例:

 	/**
     * 案例1:模拟线上环境OOM
     * 参数设置:
     * -XX:+PrintGCDetails -XX:MetaspaceSize=64m
     * -XX:+HeapDumpOnOutOfNemoryError -XX:HeapDumpPath=heap/heapdump.hprof
     * -XX:+PrintGCDateStamps -Xms30M -Xmx30M -Xloggc:log/gc-oomHeap.log
     *
     */
    @RequestMapping("/oomTest")
    public void addObject(){
        System.err.println("oomTest"+peopleSevice);
        ArrayList<People> people = new ArrayList<>();
        while (true){
            people.add(new People());
        }
    }

2.2、元空间的溢出

原因: 1.运行期间生成了大量的代理类,导致方法区被撑爆,无法卸载 2.应用长时间运行,没有重启 3.元空间内存设置过小

解决方法: 1.运行期间生成了大量的代理类,导致方法区被撑爆,无法卸载 2.应用长时间运行,没有重启 3.元空间内存设置过小

案例:

	/**
     * 案例2:模拟元空间OOM溢出
     *参数设置:
     * -XX:+PrintGCDetails -XX:MetaspaceSize=60m -XX:MaxMetaspaceSize=60m-XSS512K -XX:+HeapDumponOutOfMemoryErrorl
     * -XX:HeapDumpPath=heap/heapdumpMeta.hprof -xx:SurvivorRatio=8
     * -XX:+TraceClassLoading -XX:+TraceClassUnloading -XX:+PrintGCDateStamps-Xms60M -Xmx60M -Xloggc:log/gc-oomMeta.log
     */
    @RequestMapping("/metaSpaceOom")
    public void metaSpaceOom(){
        ClassLoadingMXBean classLoadingMXBean = ManagementFactory.getClassLoadingMXBean();
        while (true){
            Enhancer enhancer = new Enhancer();
            enhancer.setSuperclass(People.class);
            enhancer.setUseCache(false);
            //enhancer.setUseCache(true);
            enhancer.setCallback((MethodInterceptor) (o, method, objects, methodProxy) -> {
                System.out.println("我是加强类,输出print之前的加强方法");
                return methodProxy.invokeSuper(o,objects);
            });
            People people = (People)enhancer.create();
            people.print();
            System.out.println(people.getClass());
            System.out.println("totalClass:" + classLoadingMXBean.getTotalLoadedClassCount());
            System.out.println("activeClass:" + classLoadingMXBean.getLoadedClassCount());
            System.out.println("unloadedClass:" + classLoadingMXBean.getUnloadedClassCount());
        }
    }

2.3、GC overhead limit exceeded

原因: 这个是DK6新加的错误类型,一般都是堆太小导致的。Sun官方对此的定义:超过98%的时间用来做GC并且回收了不到2%的堆内存时会抛出此异常。本质是一个预判性的异常,抛出该异常时系统没有真正的内存溢出

解决: 1.检查项目中是否有大量的死循环或有使用大内存的代码,优化代码。 2.添加参数-XX:-UseGCOverheadLimit禁用这个检查,其实这个参数解决不了内存问题,只是把错误的信息延后,最终出现 java.lang.OutOfMemoryError: Java heap space。 3. dump内存,检查是否存在内存泄漏,如果没有,加大内存。

测试:

	/**
     *
     * 测试 GC overhead limit exceeded
     * 参数设置:
     * -XX:+PrintGCDetails -XX:+HeapDumpOnOutOfMemoryError
     * -XX:HeapDumpPath=heap/dumpExceeded.hprof
     * -XX:+PrintGCDateStamps -Xms10M -Xmx1OM-xloggc:log/gc-oomExceeded.log
     */
    public static void main(String[] args) {
        test1();

//        test2();
    }

    public static void test1() {
        int i = 0;
        List<String> list = new ArrayList<>();
        try {
            while (true) {
                list.add(UUID.randomUUID().toString().intern());
                i++;
            }
        } catch (Throwable e) {
            System.out.println("************i: " + i);
            e.printStackTrace();
            throw e;
        }
    }

	
	//回收效率大于2%所以只会出现堆空间不足
	public static void test2() {
        String str = "";
        Integer i = 1;
        try {
            while (true) {
                i++;
                str += UUID.randomUUID();
            }
        } catch (Throwable e) {
            System.out.println("************i: " + i);
            e.printStackTrace();
            throw e;
        }
    }

2.4 、线程溢出

注意:windos试不出来,超过windos上线会重启

线程创建公式: (MaxProcessMemory - JVMMemory - ReservedOsMemory) / (ThreadStackSize) = Numberof threads MaxProcessMemory  指的是进程可寻址的最大空间 VMMemory  JVM内存 ReservedOsMemory 保留的操作系统内存 ThreadStackSize 线程栈的大小

注意:在32位操作系统下当前公式遵守的 在64位操作系统下MaxProcessMemory (最大寻址空间)这个值接近无限大,所以ThreadStackSize不影响公式的值

Linux查看线程数: cat /proc/sys/kernel/pid_max    系统最大pid值,在大型系统里可适当调大 cat /proc/sys/kernel/threads-max    系统允许的最大线程数 maxuserprocess (ulimit -u)系统限制某用户下最多可以运行多少进程或线程 cat /proc/sys/vm/max_map_count

程序

public class TestNativeOutOfMemoryError {
    public static void main(String[] args) {
        for (int i = 0; ; i++) {
            System.out.println("i = " + i);
            new Thread(new HoldThread()).start();
        }
    }
}

class HoldThread extends Thread {
    CountDownLatch cdl = new CountDownLatch(1);

    @Override
    public void run() {
        try {
            cdl.await();
        } catch (InterruptedException e) {
        }
    }
}

三、性能优化

3.1性能监控

一种以非强行或者入侵方式收集或查看应用运营性能数据的活动。 监控前,设置好回收器组合,选定CPU(主频越高越好),设置年代比例,设置日志参数(生产环境中通常不会只设置一个日志文件)。比如: -Xloggc:/opt/xxx/logs/xxx-xxx-gc-%t.log -XX:+UseGCLogFileRotation -XX:NumberOfGCLogFiles=5 -XX:GCLogFileSize=20M -XX:+PrintGCDetails -XX:+PrintGCDateStamps -XX:+PrintGcCause

问题:

  • GC频繁
  • cpu load过高
  • OOM
  • 内存泄漏
  • 死锁
  • 程序响应时间过长

3.2性能分析

  • 打印GC日志,通过GCviewer或者gceasy.io来分析日志信息
  • 灵活运用命令行工具,jstack, jmap, jinfo等
  • dump出堆文件,使用内存分析工具分析文件(jconsole/jvisualvm/jprofiler)
  • 使用阿里Arthas,或jconsole,JVisualVM来实时查看JVM状态
  • jstack查看堆栈信息

3.3性能调优

  • 适当增加内存,根据业务背景选择垃圾回收器
  • 优化代码,控制内存使用
  • 增加机器,分散节点压力
  • 合理设置线程池线程数量
  • 使用中间件提高程序效率,比如缓存,消息队列等