文档

使用监控和部署工具对JVM进行性能调优

更新时间:
一键部署

本文主要介绍结合监控工具和应用托管工具如何高效的进行应用层JVM的优化和验证。

前提条件

示例基于阿里云专有云3.14及以上版本,本最佳实践主要涉及产品包括分布式应用服务EDAS和应用实时监控服务ARMS。

背景信息

运行一个Java应用程序,须先安装JDK或者JRE包。这是因为Java应用在编译后会变成字节码,然后通过字节码运行在JVM中,而JVM是JRE的核心组成部分。JVM不仅承担了Java字节码的分析(JIT compiler)和执行(Runtime),同时也内置了自动内存分配管理机制。它可以大大降低手动分配回收机制可能带来的内存泄露和内存溢出风险,使Java开发人员不需要关注每个对象的内存分配以及回收,从而更专注于业务本身。JVM自动内存分配管理机制的好处很多,但实则是把双刃剑。这个机制在提升Java开发效率的同时,也容易使Java开发人员过度依赖于自动化,弱化对内存的管理能力,这样系统就很容易发生JVM的堆内存异常,垃圾回收(GC)的方式不合适以及GC次数过于频繁等问题,这些都将直接影响到应用服务的性能。因此,要进行JVM层面的调优,就需要有APM工具来获取内存分配及线程调度的实时监控数据,这样在遇到问题时,我们才能通过监控数据快速地定位问题,通过部署工具来调整JVM参数并快速部署观察优化的效果,APM工具和部署工具配合使用,让排障和优化工作得以快速有效的进行。

应用场景

应用性能优化,使用压测工具(PTS)对应用进行压力测试,根据应用是响应时间优先还是吞吐量优先的策略观察JVM监控数据,根据应用性能优化目标和JVM的监控指标数据,在不修改代码的情况下调整JVM参数,对应用进行重新部署观察优化结果,直至达到优化目标。

问题排障,应用发生OOM、堆栈溢出等JVM相关的异常,通过JVM监控指标数据快速定位问题,如果是内存分配不合理导致,通过部署工具(EDAS)调整JVM的内存分配策略并重新部署应用,如果确定是代码级别的异常,可以结合接口快照功能定位到具体代码位置。在代码调整完成后,通过部署工具(EDAS)重新部署应用,快速修复问题。

实践步骤

  1. 准备阶段。

    使用分布式应用服务EDAS部署应用并开启高级监控,使用PTS对应用进行压力测试,本部分内容参考最佳实践《应用压力测试与性能评估最佳实践》。

    1. 登录Apsara Uni-manager运营控制台。1

    2. 单击产品>监控运维>应用实时监控服务ARMS,进入应用实时监控服务ARMS控制台。2

    3. 在应用实时监控服务ARMS控制台,单击左侧导航栏应用监控>应用列表,单击需要操作的应用名称,进入应用总览页。1

    4. 单击应用详情>JVM监控1

      在此模块可查看GC瞬时次数、GC瞬时耗时、堆内存详情、非堆内存详情、JVM线程数。

  2. 观察JVM监控指标数据。

    1. 垃圾回收核心指标。5如上图所示,垃圾回收核心的监控指标包括FullGC/YoungGC次数以及耗时。

      1. YoungGC(新生代GC):指发生在新生代的垃圾收集动作,新生代中的对象朝生夕死,所以相对比较频繁,回收速度也较快。YoungGC一般发生次数较多持续时间很短。对于响应时间敏感型应用建议关注YoungGC持续时间,对于吞吐敏感型应用建议关注YoungGC的发生频次,可以通过修改JVM参数中新生代内存大小来控制YoungGC的发生次数和持续耗时。

      2. FullGC(老年代GC):指发生在老年代的GC,速度一般比Minor GC慢十倍以上。FullGC会Stop-The-World。因为FullGC会暂停应用,对于响应敏感型应用要特别关注FullGC的发生次数,频繁发生FullGC会造成访问卡顿,业务高峰期的卡顿会造成请求堆积,响应超时,可以通过修改JVM参数中老年代内存大小来控制FullGC的发生次数和持续耗时。

    2. 内存分配指标。6在触发YoungGC或者FullGC时,如果要查看内存区域的实际分配情况可以看上图。

      1. 新生代内存区域:新生代用来存放新近创建的对象,特点是对象更新速度快,在短时间内产生大量的“死亡对象”。新生代几乎是所有Java对象出生的地方,即Java对象申请的内存以及存放都是在这个地方。当对象在Eden出生后,在经过一次YoungGC后,如果对象还存活,并且能够被另外一块Survivor区域所容纳,则使用复制算法将这些仍然还存活的对象复制到另外一块Survivor区域中,然后清理所使用过的Eden以及Survivor区域,并且将这些对象的年龄设置为1,以后对象在Survivor区每熬过一次YoungGC,就将对象的年龄 + 1,当对象的年龄达到某个值时 ( 默认是15岁,可以通过参数-XX:MaxTenuringThreshold来设定 ),这些对象就会进入老年代。 但这也不是一定的,对于一些较大的对象 ( 即需要分配一块较大的连续内存空间 ) 则是直接进入到老年代。

      2. 老年代:FullGC是发生在老年代的垃圾收集动作,所采用的是标记-清除算法,FullGC发生的次数不会有Minor GC那么频繁,并且做一次FullGC要比进行一次Minor GC的时间更长。 另外,标记-清除算法收集垃圾的时候会产生许多的内存碎片 ( 即不连续的内存空间 ),此后需要为较大的对象分配内存空间时,若无法找到足够的连续的内存空间,就会提前触发一次FullGC的收集动作。

    3. 线程状态及数量指标。7在对应用中的线程和同步效率作性能分析时,有两点需要注意:总的线程数和线程花在等待锁或者其他资源上的时间。通过线程监控可以发现当前是否有线程泄露,是否CPU过于繁忙,是否有大量线程在排队等待IO(大部分情况是网络IO和磁盘IO),通过以上分析基本可以判断当前的性能瓶颈点在哪里。下面对各个线程状态做下说明:

      Java语言定义了6种线程状态,在任意一个时间点中,一个线程只能有且只有其中的一种状态,并且可以通过特定的方法在不同状态之间转换。这6种状态分别是:

      • 新建(New):创建后尚未启动的线程处于这种状态。

      • 运行(Runnable):包括操作系统线程状态中的Running和Ready,也就是处于此状态的线程有可能正在执行,也有可能正在等待着操作系统为它分配执行时间。

      • 无限期等待(Waiting):处于这种状态的线程不会被分配处理器执行时间,它们要等待被其他线程显式唤醒。以下方法会让线程陷入无限期的等待状态。

        • 没有设置Timeout参数的Object::wait()方法。

        • 没有设置Timeout参数的Thread::join()方法。

        • LockSupport::park()方法。

      • 限期等待(Timed Waiting):处于这种状态的线程也不会被分配处理器执行时间,不过无须等待被其他线程显式唤醒,在一定时间之后它们会由系统自动唤醒。以下方法会让线程进入限期等待状态:

        • Thread::sleep()方法。

        • 设置了Timeout参数的Object::wait()方法。

        • 设置了Timeout参数的Thread::join()方法。

        • LockSupport::parkNanos()方法。

        • LockSupport::parkUntil()方法。

      • 阻塞(Blocked):线程被阻塞了,“阻塞状态”与“等待状态”的区别是“阻塞状态”在等待着获取到一个排它锁,这个事件将在另外一个线程放弃这个锁的时候发生;而“等待状态”则是在等待一段时间,或者唤醒动作的发生。在程序等待进入同步区域的时候,线程将进入这种状态。

      • 结束(Terminated):已终止线程的线程状态,线程已经结束执行。

  3. 修改JVM配置参数重新部署观察优化效果。

    EDAS在部署或者升级Java应用时提供图形化界面来设置JVM参数,用户无需再去记忆繁琐冗长的参数,通过界面方式设置不但简单易用也可以避免因参数错误导致的生产环境故障。

    1. 登录Apsara Uni-manager运营控制台:8

    2. 单击产品>中间件>企业级分布式应用服务EDAS,进入EDAS控制台;9

    3. 在左侧导航栏中单击应用管理>应用列表,单击需要操作的应用名称,进入应用详情;10

    4. 单击部署应用,进入部署详情,选择对应的部署模式;1112

    5. 在发布配置页,选择Java启动参数配置,单击编辑,进行编辑。13

      1. 设置各个内存区域的大小,包括堆内存大小、新生代和老年代的大小,通过监控发现的问题,可以通过不断调整各个区域内存大小再不断压测观测监控数据的方式进行性能调优。14

      2. 设置垃圾回收算法,根据应用是响应敏感还是吞吐敏感来设置不同的垃圾回收算法。15

      3. 设置垃圾回收日志的日志存放目录,以及保留个数和文件大小,便于详细分析回收过程已制定更加有效的优化策略。16

      4. 对于界面没有提供的JVM参数,可以通过自定义方式添加。17

        通过上述配置Java启动参数有助于降低GC(垃圾回收)开销,从而缩短服务器响应时间并提高吞吐量。

  • 本页导读
文档反馈