java虚拟机12

压测工具AB

Ab(Apache Bench)

  • 安装
    • yum -y install httpd-tools
  • 测试 get 请求接口
    • ab -c 10 -n 100 http://www.test.api.com/test/login?userName=test&password=test
    • -c后面的10是10个并发,-n后面的100是100个总请求
  • 测试 post请求接口
    • ab -c 10 -n 100 -p ‘post.txt’ -T ‘application/x-www-form-urlencoded’ ‘http://www.test.api.com/test/register’
    • post.txt是post方法的请求体
  • 工具的输出,其中的一些核心参数
    • cocurent level,并发量
    • requests per second,每秒的吞吐量
    • time per requests,其中这个参数有两个,较大的那一个是用户的平均请求时间,较小的那一个是服务器平均处理时间
    • connection time,平均的连接时间
    • percentage of the requests served within a certain time(ms),多少百分比的请求在多少毫秒以内
  • 参数的含义
  • 性能指标

Linux服务器

  • 虚拟机配置的
    • 2核2G内存

内存调优案例

案例介绍

  • 模拟抢购接口

    • package cn.enjoyedu.controller;
      
      import org.springframework.web.bind.annotation.RequestMapping;
      import org.springframework.web.bind.annotation.RequestParam;
      import org.springframework.web.bind.annotation.RestController;
      
      import java.util.ArrayList;
      import java.util.List;
      
      /**
       * 类说明:
       */
      @RestController
      @RequestMapping("/jvm")
      public class TestController {
              
              
          @RequestMapping("/heap")
          public String test(){
              
              
              List<Byte[]> list = new ArrayList<Byte[]>();
              Byte[] b = new Byte[1024*1024];
              list.add(b);
              return "success";
          }
      }
      
      
    • 模拟抢购、秒杀情况,此时会创建订单,然后会新建很多对象(5W的并发,创建的对象大概是20kb),这里模拟这种情况,就会创建一个1M大小的list,并发模拟是1000

  • 案例代码

服务器配置信息

  • 2核2G

GC监控

  • 因为需要压测,关心垃圾回收器的性能

    • jstat可以统计gc的情况

    • jstat -gc 进程号 5000 20

      每隔5000ms刷新20次

    • jstat -gc 进程号 5000 20 | awk ‘{print $13,$14,$15,$16,$17}’

      只显示13列,14列,15列,16列,17列,最后只显示younggc次数、younggc的时间、fullgc次数、fullgc的时间、总共gc花费的时间

堆空间监控

  • springboot项目启动后,内存空间默认是多少?
    • java -XX:+PrintFlagsFinal -version | grep HeapSize
    • 发现堆的初始大小是32M,堆的最大大小是480M
  • jmap -heap 进程号
      • eden区,128M
      • from区,3M
      • to区,4.5M
      • old区,18.5M
    • 为什么from区和to区不相等
      • 因为linux中默认垃圾回收器是Parallel Scavenge和Parallel old,这种回收器会自动调整eden:from:to区的比例,不一定就是8:1:1,但是这种处理gc的时候会stop the world,降低服务的吞吐量

测试案例场景

  • 10个并发用户/10万请求量

    • ab -c 10 -n 100000 http://127.0.0.1:8080/jvm/heap
  • 100个并发用户/10万请求量

    • ab -c 100 -n 100000 http://127.0.0.1:8080/jvm/heap
  • 1000个并发用户/10万请求量

    • ab -c 1000 -n 100000 http://127.0.0.1:8080/jvm/heap

内存调优压测及优化过程

  • 测试时第一次算热身,第二次的测试结果才会填入

优化方案一

  • java -jar -Xms1500m -Xmx1500m jvm-1.0-SNAPSHOT.jar

  • 堆加大,-Xms1500m,-Xmx1500m

    • 因为服务器本身只有2g,这差不多是极限值了
  • 重启项目

测试结果

  • 相比默认堆内存,不管是哪个数量级下,性能都降低了

优化方案二

  • java -jar -Xms1500m -Xmx1500m -Xmn1000m -XX:SurvivorRatio=8 jvm-1.0-SNAPSHOT.jar

  • 堆加大,新生代加大到1g,eden区和from区、to区的比例恒定为8:1:1,-Xms1500m,-Xmx1500m,-Xmn1000m,-XX:SurvivorRatio=8

测试结果

  • 在低并发情况下性能略低一点,在高并发情况下性能有提升

  • 参数配置 性能指标 10个并发/10万总请求 100个并发/10万总请求 1000个并发/10万总请求
    默认值 吞吐量 1050次每秒 872次每秒 781次每秒
    服务器平均处理时间 0.952ms 1.146ms 1.279ms
    GC耗时 2703次younggc,29秒,30次fullgc,1.6秒,总共30.6秒 2722次younggc,51.5秒,57次fullgc,3.8秒,总共55.3秒 2995次younggc,60秒,11次fullgc,1秒,总共61秒
    优化方案一 吞吐量 753次每秒 620次每秒 707次每秒(超频)
    服务器平均处理时间 1.326ms 1.61ms 1.413ms(超频)
    GC耗时 821次younggc,55秒,2次fullgc,3.8秒,总共58.8秒 841次younggc,79秒,9次fullgc,8秒,总共87秒 总共74秒(超频)
    优化方案二 吞吐量 1025次每秒 923次每秒 1024次每秒
    服务器平均处理时间 0.975ms 1.08ms 0.976ms
    GC耗时 412次younggc,16秒,2次fullgc,1.2秒,总共17.2秒 总共37秒 总共31秒

结果分析

GC 频率

  • 高频的 FullGC 会给系统带来非常大的性能消耗,虽然 MinorGC 相对 FullGC 来说好了许多,但过多的 MinorGC 仍会给系统带来压力。

内存

  • 这里的内存指的是堆内存大小,堆内存又分为年轻代内存和老年代内存。堆内存不足,会增加 MinorGC ,影响系统性能。

吞吐量

  • 频繁的 GC 将会引起线程的上下文切换,增加系统的性能开销,从而影响每次处理的线程请求,最终导致系统的吞吐量下降。

延时

  • JVM 的 GC 持续时间也会影响到每次请求的响应时间。

推荐策略

  • 在高并发情况下,所有对象都是朝生夕死的,把堆的新生代调大,老年代够用就行,eden:from:to可以调成8:1:1
  • 为什么单纯调大堆内存,效率反而降低了?
    • younggc回收有两步,首先扫描垃圾对象,然后是复制,空间大了,扫描时间也变多了,同时堆空间增大了,eden区的空间没有明显增大,由300M变成了500M
    • 而方案二中很大的新生代、小的老年代,因为测试方法对象的生命周期短,所以这种方案是适合的

如何优化GC

GC 性能衡量指标

  • 吞吐量
  • 停顿时间
  • 垃圾回收频率

GC 调优策略

  • 降低 Minor GC 频率
  • 降低 Full GC 的频率
  • 选择合适的GC回收器

分析 GC 日志

  • java -jar -XX:+PrintGCDateStamps -XX:+PrintGCDetails -Xloggc:./gclogs jvm-1.0-SNAPSHOT.jar

    • 通过这种方式创建gc日志文件
  • Gceasy这个工具收费,也可以用开源工具GCViewer

1000并发的默认方案和优化方案二对比(提前的测试结果)

  • 1.默认方案的堆内存是459M,优化方案二是1471M

  • 2.首先没有发生内存泄漏,因为在fullgc之后的堆内存使用空间在20%以下,说明空间都回收回去了,如果发生内存泄漏了,fullgc之后堆空间占用率应该还是很高的

  • 3.暂停时间对比

    • 默认方案暂停时间是58秒,方案二的暂停时间是15秒

实例说明

  • 加大新生代,会降低minorgc的频次,其中包括两部分时间,一是扫描时间t1,二是复制存活对象时间t2,两者加起来的时间就是minorgc花费的时间

  • 假设每300ms触发一次minorgc,并且一个对象在eden区能存活500ms

    • 在方案一中,由于eden区没有明显增大,minorgc还是会频繁触发,而且堆的空间又变大了,所以复制时间又会变长

    • 在方案二中,加大eden区,minorgc触发频次会变少,假设触发间隔变成600ms,对象存活时间还是500ms,由于触发间隔比较长,已经大于了对象存活时间,所以就不用复制存活对象了,因此虽然t1变大了,但是t2没有了

    • t1和t2的时间对比?

      扫描快,复制慢,因为在 JVM 中,复制对象的成本要远高于扫描成本

猜你喜欢

转载自blog.csdn.net/Markland_l/article/details/115181561