• 四:JVM调优原理与常见异常处理方案


    在jvm调优之前,我们必须先了解jvm的内存模型与GC回收机制,这些在我前面的文章里面有介绍!接下来我们通过一个案例来调整jvm性能。

    一测试案例:

      1.1 编写demo

    import java.text.DecimalFormat;
    /**
        -XX:+PrintGC         打印GC日志
        -XX:+PrintGCDetails  打印详细的GC日志
        file.encoding         文件编码
        -XX:MaxTenuringThreshold 对象年龄,默认15次之后较大几率放进入老年代,为0则不进入s0 s1区,直接进老年代
        
        -XX:+UseSerialGC       串行收集器
        -XX:+UseParallelOldGC  并行收集器
        -XX:+UseParallelGC     合并回收
        -XX:ParallelGCThreads  并行收集器线程数量
        
        -Xms               堆初始值
        -Xmx               堆最大可用值
        -XX:SurvivorRatio  新生代中eden空间和from(s0),to(s1)空间的比例
        -XX:NewRatio       新生代与老年代的比例
        -Xss  栈的大小(栈的深度)
        
        -XX:MetaspaceSize     元空间初始值
        -XX:MaxMetaspaceSize  元空间最大值
        一般生产环境都肯定会指定这两个参数
        -XX:+HeapDumpOnOutOfMemoryError:内存溢出生成快照文件
    -XX:HeapDumpPath= 存放快照路径
    */ public class JVMDemo { public static void main(String[] args) throws InterruptedException { // 最大内存 long maxMemory = Runtime.getRuntime().maxMemory(); System.out.println("最大堆内存为: "+format(maxMemory)+"MB"); // 已经使用内存 long totalMemory = Runtime.getRuntime().totalMemory(); System.out.println("已使用堆内存: "+format(totalMemory)+"MB"); // 当前剩余内存 long freeMemory = Runtime.getRuntime().freeMemory(); System.out.println("剩余堆内存为: "+format(freeMemory)+"MB"); byte[] b1 = new byte[4 * 1024 * 1024]; System.out.println("-----分配了4m堆内存-----"); // 当前剩余内存 long freeMemory2 = Runtime.getRuntime().freeMemory(); System.out.println("剩余堆内存为: "+format(freeMemory2)+"MB"); } /** * 将堆内存单位格式化成 MB */ static private String format(long maxMemory) { float num = (float) maxMemory / (1024 * 1024); DecimalFormat df = new DecimalFormat("0.00");// 格式化小数 String s = df.format(num);// 返回的是String类型 return s; } }

       1.2 配置参数,打印jvm信息:  右键 --> Run As --> Run Configurations... --> Arguments --> VM arguments 输入配置信息  -XX:+PrintGCDetails -XX:+UseSerialGC

      

      1.3 运行java代码, 查看jvm信息

    // 这些信息与电脑配置参数有关,我们的具体数据可能不一样,但是内存模型数据比例是一样的
    最大堆内存为: 1890.81MB  // 大约1900MB
    已使用堆内存: 119.88MB   // 已用120MB
    剩余堆内存为: 117.89MB   // 剩余117MB
    -----分配了4m堆内存-----
    剩余堆内存为: 113.89MB   // 用了4mb后还剩113MB
    Heap
     def new generation   total 38080K, used 6805K [0x0000000085c00000, 0x0000000088550000, 0x00000000ae800000)
     // 可以看出新生代中默认 eden区 from区 to区比例为 33856:4224:4224 即 8:1:1
      eden space 33856K,  20% used [0x0000000085c00000, 0x00000000862a57a8, 0x0000000087d10000)
      from space 4224K,   0% used [0x0000000087d10000, 0x0000000087d10000, 0x0000000088130000)
      to   space 4224K,   0% used [0x0000000088130000, 0x0000000088130000, 0x0000000088550000)
     // 老年代和新生代默认比例为 84672:(33856+4224+4224)  即2:1
     tenured generation   total 84672K, used 0K [0x00000000ae800000, 0x00000000b3ab0000, 0x0000000100000000)
       the space 84672K,   0% used [0x00000000ae800000, 0x00000000ae800000, 0x00000000ae800200, 0x00000000b3ab0000)
     // 元空间
     Metaspace       used 3652K, capacity 4600K, committed 4864K, reserved 1056768K
      class space    used 410K, capacity 428K, committed 512K, reserved 1048576K

    二性能调优:

    这个时候我们对jvm堆内存有了一个大致的认识,我们开始正式的性能调优。
    案例:通过jmeter压力测试工具来访问一个网站,测试系统的吞吐量。
     2.1.1:双击启动 jmeter/bin/jmeter.bat 后台不要关闭
     2.1.2: 添加线程组 (用50个线程去发起5w个请求)

       

     2.1.3: 添加http请求 指定服务器  ip   port   url。 

       

     2.1.4: 添加聚合报告

      

     2.1.5:查看测试结果 (只需要关注总请求量  和  吞吐量)

      

    2.2:串行收集器吞吐量调优

    【在tomcat里设置如下参数配置】
    打印详细日志   最大堆和初始堆为32mb
    内存溢出时生成快照文件
    串行回收
    非堆区初始大小32mb
    
    -XX:+PrintGCDetails -Xmx32M -Xms32M
    -XX:+HeapDumpOnOutOfMemoryError
    -XX:HeapDumpPath=D:appjvmlogapp.dump -XX:+UseSerialGC -XX:PermSize=32M
    (1.8以前的永久区写法)
    
    

     

    2.3:并行收集器吞吐量调优

    【在tomcat设置如下参数】
    打印详细日志   最大堆和初始堆为1000mb
    内存溢出时生成快照文件
    并行合并回收
    并行收集器线程数量为8个
    非堆区初始大小500mb
    
    -XX:+PrintGCDetails -Xmx1000M -Xms1000M
    -XX:+HeapDumpOnOutOfMemoryError
    -XX:HeapDumpPath=D:appjvmlogapp.dump
    -XX:+UseParallelGC -XX:+UseParallelOldGC -XX:ParallelGCThreads=8 -XX:PermSize=500M (1.8以前的永久区写法)

    对比可以看出:第一次调优由于堆内存太小而经常触发GC,且由于是串行清理,吞吐量仅仅只有131;而第二次加大了堆内存几乎不触发GC清理,且由于是并行清理所以吞吐量提升到了2482。

    调优总结:  

      1.将初始的堆大小与最大堆大小相等,来垃圾回收次数从而提高效率。 
      2.根据自身服务器性能,将堆内存最大化,以此提高吞吐量。
      3.将新生代或老年代的比例设置为1/2 或者 1/3,让GC尽量去新生代去回收。
      4.合理的使用并行收集器。数量和CPU核数相同,例如8核的CPU就设置为8,既提高了工作效率又避免了上下的频繁切换。

    在windows中设置JVM参数

    修改 tomcat/bin/catalina.bat 文件
    set JAVA_OPTS=-Dfile.encoding=UTF-8 -server -XX:+PrintGCDetails -Xms900m -Xmx900m -XX:SurvivorRatio=8 -XX:NewRatio=2 -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=D:appjvmlogapp.dump -XX:+UseParallelGC -XX:+UseParallelOldGC -XX:ParallelGCThreads=8 -XX:MaxMetaspaceSize=128m 

    在linux中设置JVM参数

    修改 tomcat/bin/catalina.sh 文件
    JAVA_OPTS="-Dfile.encoding=UTF-8 -server 
               -XX:+PrintGCDetails 
               -Xms1000m -Xmx1000m 
               -XX:SurvivorRatio=8 -XX:NewRatio=2 
               -XX:MaxMetaspaceSize=200m
               -XX:+HeapDumpOnOutOfMemoryError
           -XX:HeapDumpPath=/app/jvmlog/app.dump -XX:+UseParallelGC -XX:+UseParallelOldGC -XX:ParallelGCThreads=8"

     

     生产环境定位问题

      1. 我们启动项目的时候,可以设置jvm参数使内存溢出时生成dump快照文件(事先创建好输出的目录文件夹 /log)

    #!/bin/sh
    #指定项目路径
    export project_path=/usr/local/wulei/demo
    #指定jar包名称
    export JAR_NAME=app.jar
    #指定java环境变量
    export JAVA_HOME=/usr/local/wulei/jdk8

    #清空之前的启动日志
    echo "" > $project_path/nohup.out

    echo -e "33[47;34;5m ======= 开始启动项目..... ======= 33[0m"
    #启动脚本
    nohup $JAVA_HOME/bin/java -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/log/app.dump -Xms20m -Xmx20m -jar $project_path/$JAR_NAME 2>&1 &

    #打印启动日志
    tail -f $project_path/nohup.out

      2. 可以看到OOM异常后生成了dump文件,我们可以通过jdk的jvisualvm.exe工具来分析问题。 

      

      3. 根据stack堆栈调用轨迹,可以查看到哪一块代码在死锁,等待,修改代码。点进去查看详细信息能看到具体是  Web类的第16行的数组对象造成的。

       

    ====================================================================

    什么是内存泄漏及如何避免

         内存泄漏即:对象可达但不可用,程序不需要用某个对象,但另一个正在使用的对象却持有它的引用,导致无法回收停留在堆内中。

         防止内存泄露:

          1.尽早释放无用对象的引用, 将不需要使用的对象设置为null,暗示垃圾收集器来收集该对象,防止发生内存泄漏。  

          2.程序进行字符串处理时,尽量避免使用String,而应该使用StringBuffer。

            3.尽量少用静态变量

          4.尽量运用对象池技术以提高系统性能

    Jvm常见异常处理方案

       内存泄漏: java.lang.OutOfMemory Error:Java heap space

           解决思路:  1. 先查看是不是内存泄漏,如果是通过GC Root的路径来排查

                2. 查看堆内存是否有对象没释放。

                3. 加大物理内存 –Xms, -Xmx,最好-Xms = -Xmx,减少内存扩展的开销。

                     控制参数:  -Xms (starting 堆的起始大小)         -Xmx (max 堆的最大大小)         -Xmn (new 堆的新生代大小)

      内存溢出: java.OutOfMemory Error:PermGen space

                      解决思路:  增加参数:-XX:PrintGCDetails,-XX:+PrintGCTimeStamps和-XX:+PrintGCDateStamps 

      线程请求的栈深度大于虚拟机所允许的最大深度java.lang.StackOverflow Error

          解决思路: 可以将栈的深度,理解为数组的长度。

          控制参数:  1. 加大-Xss(每个线程的堆栈大小)参数。  

               2. 更换64位虚拟机。

               3. 减少线程。        

               4. 减少最大堆(Xmx)。

             java.lang.OutOfMemory Error,有allocate、Native字样

         解决思路:  加大本地内存-MaxDirectMemorySize如不指定则与-Xmx一致。

        以centos6.8 ,100G的内存 jdk1.7为例,参考配置如下:

          JAVA_OPTS="-Xms8g -Xmx8g

                -XX:ParallelGCThreads=8

                -XX:PermSize=2g

                -XX:MaxPermSize=4g

                -Xss512k -Xmn6g

                -XX:-DisableExplicitGC

                -XX:+UseCompressedOops

                -XX:+UseConcMarkSweepGC

                -XX:+CMSParallelRemarkEnabled"

       CATALINA_OPTS="-Xms8g -Xmx8g

                -XX:ParallelGCThreads=8

                -XX:PermSize=2g

                -XX:MaxPermSize=4g

                -Xss512k -Xmn6g

                -XX:-DisableExplicitGC

                -XX:+UseCompressedOops

                -XX:+UseConcMarkSweepGC

                -XX:+CMSParallelRemarkEnabled"

  • 相关阅读:
    在docker容器中访问宿主机端口
    springcloud ActiveMQ设置多个并行消费者
    Spring boot activeMQ 设置并行消费
    redis命令行如何清空缓存(windows环境下)
    一文读懂PostgreSQL-12分区表
    PostgreSQL 那些值得尝试的功能,你知道多少?
    Windows如何设置或更改PostgreSQL数据目录位置
    postgresql 致命错误: 已保留的连接位置为执行非复制请求的超级用户预留
    为什么没有插入数据,但已用存储空间会增加
    postgresql批量修改表的owner
  • 原文地址:https://www.cnblogs.com/wlwl/p/9941196.html
Copyright © 2020-2023  润新知