本文 首发于 🍀 永浩转载 请注明 来源

JVM调优

今天要不来聊聊JVM调优相关的吧?你曾经在生产环境下有过调优JVM的经历吗?

  • 没有

  • 嗯.是这样的,我们一般优化系统的思路是这样的

    • 1.一般来说关系型数据库是先到瓶颈,首先排查是否为数据库的问题

      • (这个过程中就需要评估自己建的索引是否合理、是否需要引入分布式缓存是否需要分库分表等等)
    • 2.然后,我们会考虑是否需要扩容(横向和纵向都会考虑)

      • (这个过程中我们会怀疑是系统的压力过大或者是系统的硬件能力不足导致系统频繁出现问题)
    • 3.接着,应用代码层面上排查并优化

      • 扩容是不能无止境的,里头里外都是钱阿,这个过程中我们会审视自己写的代码是否存在资源浪费的问题,又或者是在逻辑上可存在优化的地方,比如说通过并行的方式处理某些请求)
    • 4.再接着,JVM层面上排查并优化

    • 审视完代码之后,这个过程我们观察JVM是否存在多次GC问题等等)

    • 5.最后,网络和操作系统层面排查

      • (这个过程查看内存CPU/网络/硬盘读写指标是否正常等等)
  • 绝大多数情况下,到第三步就结束了, 一般经过「运维团队」给我们设置的JVM和机器上的参数,已经满足绝大多数的需求了。

    调优顺序

  • 之前有过其他团队在「大促」发现接口处理超时的问题,那吋候查各种监控怀疑是 FULL GC频率稍大所导致的

    • 第一想法不是说去调节各种JVM参数来进行优化,而是直接加机器
    • (用最粗暴的方法,解决问题是最简单的,扩容)
    • 不过,我是学过JM相关的调优命令和思路的。
    • 在我的理解下,调优JVM其实就是在「理解」JM内存结构以及各种垃圾收集器前提下,结合自己的现有的业务来「调整参数」,使自己的应用能够正常稳定运行
  • jvm调优

    • 一般调优JVM我们认为会有几种指标可以参考:『吞吐量』、『停顿时间』和垃圾回收频率』

    • 基于这些指标,我们就有可能需要调整

      • 1.内存区域大小以及相关策略(比如整块堆内存占多少、新生代占多少、老年代占多少、 Survivor占多少、晋升老年代的条件等等
        • 比如(-Xmx:设置堆的最大值、-Xms:设置堆的初始值、-Xmn:表示年轻代的大小、- XX: Survivorratio:伊甸区和幸存区的比例等等
        • 按经验来说:IO密集型的可以稍微把「年轻代」空间加大些,因为大多数对象都是在年轻代就会灭亡。内存计算密集型的可以稍微把「老年代」空间加大些,对象存活时间会更长些)
      • 2.垃圾回收器(选择合适的垃圾回收器,以及各个垃圾回收器的各种调优参数)
        • 比如(-XX:+UseG1GC:指定JVM使用的垃圾回收器为G1、- XX: Maxgcpause Miliis:设置目标停顿时间、-XX:InitiatingHeapoccupancypercent:当整个堆内存使用达到一定比例,全局并发标记阶段就会被启动等等)
        • 没错,这些都是因地制宜,具体问题具体分析(前提是得懂JVM的各种基础知识,基础知识都不懂,谈何调优)
        • 在大多数场景下,JWM已经能够达到「开箱即用」

      调优

    • 一般我们是「遇到问题」之后才进行调优的,而遇到问题后需要利用各种的「工具」进行排查

      • 1.通过ps命令查看Java进程「基础」信息(进程号、主类)。这个命令很常用的就是用来看当前服务器有多少Java进程在运行,它们的进程号和加载主类是啥
      • 2.通过stat命令査看Java进程「统计类」相关的信息(类加载、编译相关信息统计,各个内存区域GC概况和统计)。这个命令很常用于看GC的情况
      • 3.通过jnfo命令来查看和调整Java进程的「运行参数」
      • 4.通过imap命令来査看Java进程的「内存信息」。这个命令很常用于把JVM内存信息dump到文件,然后再用MAT(Memory Analyzer tool内存解析工具)把文件进行分析
      • 5.通过 stack命令来查看JVM「线程信息」。这个命令用常用语排查死锁相关的问题
      • 6.还有近期比较热门的 Arthas(阿里开源的诊断工具),涵盖了上面很多命令的功能且自带图形化界面。这也是我这边常用的排查和分析工具

    监控工具

之前聊JVM的时候,你也提到过在「解释」阶段,会有两种方式把字节码信息解释成机器指令码,一个是字节码解释器、一个是即时编译器(JIT)。我想问问,你了解JVM的JT优化技术嘛?

  • JT优化技术比较出名的有两种:方法内联和逃逸分析

    • 所谓方法内联就是把「目标方法」的代码复制到「调用的方法」中,避免发生真实的方法调用
      • 因为每次方法调用都会生成栈帧(压栈出栈记录方法调用位置等等)会带来定的性能损耗,所以「方法内联」的优化可以提高一定的性能
      • 在JVM中也有相关的参数给予我们指定(-XX: Maxfreainlinesize-xx: Maxinliresize等等)
    • 而「逃逸分析」则是判断一个对象是否被外部方法引用或外部线程访问的分析技术,如果「没有被引用」,就可以对其进行优化
  • 下面我举几个可优化的例子(思路)

    1.锁消除(同步忽略):该对象只在方法內部被访问,不会被别的地方引用,那么就一定是线程安全的,可以把锁相关的代码给忽略掉

    2.栈上分配:该对象只会在方法內部被访问,直接将对象分配在「栈」中(Java默认是将对象分配在「堆」中,是需要通过JM垃圾回收期进行回收,需要损耗一定的性能,而栈内分配则快很多)

    3.标量替换分离对象:当程序真正执行的时候可以不创建这个对象,而直接创建它的成员变量来代替。将对象拆分后,可以分配对象的成员变量在栈或寄存器上,原本的对象就无需分配内存空间了

    JIT常见优化