java调优基础

167 阅读5分钟

java应用性能的瓶颈点有点多,比如磁盘,内存,网络IO等因素,java应用代码,JVM GC,数据库,缓存等。基本可以划分为以下四个层级,每层优化难度增加

  1. 应用层,需要理解代码业务逻辑,通过java线程栈定位问题代码行
  2. 数据库层,需要分析sql,定位死锁
  3. 框架层,需要懂源代码,理解框架机制
  4. JVM层,需要对GC类型及工作机制了解

一 性能诊断工具

  • 针对java应用,性能争端工具主要分为两层:OS层和java应用层
  • OS诊断主要关注的是CPU,Memory,IO三个方面
    1. CPU
      • 对于CPU主要关注平均负载,CPU使用率,上下文切换次数
      • 通过top命令可以查看系统平均负载和CPU使用率
        • 按照经验,若数值小于0.7 * cpu个数,则系统正常工作,若超过这个值,甚至达到CPU核数的四五倍,则系统的负载就明显偏高
      • 通过vmstat命令可以查看CPU的上下文切换次数
        • 上下文切换次数发生的场景有如下几种
          1. 时间片用完,CPU正常调度下一个任务
          2. 被其他优先级更高的任务抢占
          3. 执行任务碰到IO阻塞,挂起当前任务,切换到下一个任务
          4. 用户代码主动挂起当前任务让出CPU
          5. 多任务抢占资源,由于没有抢到被挂起
          6. 硬件中断
        • java线程上下文切换主要来自共享资源的竞争。一般单个对象加索很少成为系统瓶颈,除非锁粒度过大。但是在一个访问频度高,对多个对象连续加锁的代码块中就可能出现大量上下文切换,成为系统瓶颈
    2. Memory
      • 从操作系统角度,内存关注应用进程是否足够,可以使用free -m命令查看内存的使用情况
      • 通过top命令可以查看进程使用的虚拟内存VIRT和物理内存RES
      • 对于java应用来说,占用太多交换分区可能会影响性能
    3. IO
    • IO包括网络IO和磁盘IO,一般情况下磁盘IO更容易出现IO瓶颈
    • 通过iostat可以查看磁盘读写情况,通过CPU IO wait可以看出磁盘IO是否正常
    • 如果磁盘IO一直处于很高的状态,说明磁盘太慢或者故障,成为了性能瓶颈,需要进行优化或者磁盘更换
    • 除了常用的top,ps,vmstat,iostat等命令,还有其他linux工具可以诊断系统问题,如mpstat,tcpdump,netstat,pidstat,sar等
  • java应用诊断及工具
    • 可以直接定位有问题的代码
    • 可以通过top+jstack找出有问题的线程栈,定位到问题线程的代码上
    • 可以通过Stopwatch打印性能日志定位代码
    • 常用的java应用诊断包括线程,堆栈,GC等
    • jstack
      • jstack命令通常配合top使用,通过top -H -p pid定位java进程或线程,再利用jstack -l pid导出线程栈。由于线程栈时瞬态的,因此需要多次dump,一般需要3次,每次间隔5秒
      • 阿嘎
    • JProfiler
      • JProfile可对CPU,堆,内存进行分析,功能清大。同时结合压测工具,可以对代码耗时进行统计
    • GC诊断
      • Java GC解决了程序员管理内存的风险,但GC引起的应用暂停成了另外一个问题。JDK提供了一系列工具来定位GC问题,比较常用的有jstat,jmap还有第三方工具MAT等
        • jstat
          • jstat命令可以打印GC详细信息,Young GC和Full GC次数,堆信息等。其命令格式为jstat -gcxxx -t pid
        • jmap
          • jmap打印java进程堆信息jmap -heap pid。通过jmap -dump:file=xxx pid可以dum堆到文件,然后通过其他工具进一步分析其堆的使用情况
        • MAT
          • MAT时java堆的分析力气,提供了直观的诊断报告,内置的OQL允许对堆进行类SQL查询,功能强大

二 性能优化实践

2.1 JVM调优:GC

  • GC 调优目标基本有三个思路:
    1. 降低 GC 频率,可以通过增大堆空间,减少不必要对象生成;
    2. 降低 GC 暂停时间,可以通过减少堆空间,使用 CMS GC 算法实现;
    3. 避免 Full GC,调整 CMS 触发比例,避免 Promotion Failure 和 Concurrent mode failure(老年代分配更多空间,增加GC线程数加快回收速度),减少大对象生成等

2.1.1 案例一

  • RMI 的 GDC(Distributed Garbage Collection,分布式垃圾收集)会启动守护线程定期执行 Full GC 来回收远程对象
  • 解决方案:
    1. 一种是通过增加-XX:+DisableExplicitGC 参数,直接禁用系统 GC 的显示调用,但对使用 NIO 的系统,会有堆外内存溢出的风险
    2. 另一种方式是通过调大 -Dsun.rmi.dgc.server.gcInterval 和-Dsun.rmi.dgc.client.gcInterval 参数,增加 Full GC 间隔,同时增加参数-XX:+ExplicitGCInvokesConcurrent,将一次完全 Stop-The-World 的 Full GC 调整为一次并发 GC 周期,减少应用暂停时间,同时对 NIO 应用也不会造成影响

2.2 应用层调优

2.2.1 案例一

  • HashMap 本身并不具备多线程并发的特性,在多个线程同时put操作的情况下,内部数组进行扩容时会导致 HashMap 的内部链表形成环形结构,从而出现死循环

2.3 数据库调优

三 java性能优化的50个细节