《java performance》翻译 第七章jvm调优:基础概念

    现代jvm是一个能适应各种不同复杂应用的软件,大多数的应用在jvm的默认配置上能够运行得很好、满足需求,但还是会有一些应用在需要在默认配置上进行适当的jvm参数调优才能满足应用的性能需求。现代的jvm中有大量的参数可以设置进而改变jvm的行为,不幸得是,适合一个应用上的最佳配置未必在另一个应用上能够起作用。所以,知道怎么调优jvm参数就变得很重要。
     方法论
    调优的第一步是要找出当前需要调优应用的系统级别的调优需求。它包括系统的吞吐量、响应时间、使用的内存数量、启动时间、可用性、可控性等。
    下一阶段就要对这些系统需求进行详细的分析和取舍。因为对jvm进行性能调优本身也是一个权衡取舍的过程,在你强调了一方面时很有可能是要以牺牲另一方面的表现为代价的,这个过程当中,主要要取舍各种需求的重要性。比如你希望应用更少得内存空间,那应用就响应得会在响应时间和吞吐量上受到影响。如果你为了可控性从而减少应用部署的机器,那就可能在大流量上对应用的集群造成额外的风险。所以,在了解了系统需求之后,需要在分析需求阶段确认那个需求是性能调优最需要关注的。
    当确定了最重要的调优需求之后,接着你就需要确定应用的发布模型,是把应用部署在集群的多台机器上呢,还是只部署一台机器。可用性,可控性等决定了你的部署方式。
    紧接着要确定的是jvm的运行环境,jvm运行环境包括32位机上的client模式(较快的启动速度和较小的内存占用空间)以及32位或者64位机器上的server模式(更高的吞吐量)。吞吐量、响应速度、启动时间等因素决定了jvm运行环境的选择。
    下一个阶段则要来设置jvm的垃圾回收相关参数,它通常由内存使用情况、响应速度、吞吐量这些因素(排名分先后)决定。
    调优过程需要在以上的步骤上持续得进行迭代,经过不断得进行数据采样和修改配置,经过多次的迭代达到最终优化的目的。
    测试设施要求
    为了保证调优阶段搜集的信息的准确性,需要在硬件环境和负载上都能够尽量得和线上保持接近甚至相同,比如保证机器硬件配置和生产环境的一致,调优时通过模拟的方式模拟系统负载和生产环境的一致性。
    系统调优需求
    在方法论阶段介绍了各种调优的需求,包括系统的吞吐量、响应时间、使用的内存数量、启动时间、可用性、可控性等。接下来从各个需求入手介绍针对每一种需求进行调优的手段。
    可用性
    可用性是对系统可操作状态和不可使用状态的衡量。可用性强调应用系统在某些模块应用不能工作或者是遇到其他问题时其他模块依然能正常提供使用的能力。
    在java应用中,高可用性一般都是通过把应用部署在多个不同的jvm实例上来达到的。部署多个jvm实例的一项弊端是增加了可控性的复杂度。因为当部署的jvm数量增加时,对jvm进行管理的花费也就增加了。可用性需求的一个例子是“当应用所在机器上的某个软件部分出现异常导致应用无法提供访问时,不能导致应用提供所有服务”。
     可控性
    可控性是对系统在运行过程当中进行监控和配置上花费的衡量。可控性体现在尽可能少得部署jvm,这就和可用性需求有冲突,因为可用性需要通过部署更多的jvm实例来更好得保证的。
    吞吐量
    吞吐量是系统在单位时间能能够处理请求的数量。通常,吞吐量的提升需要牺牲系统的响应时间和内存占用。
    响应时间
    系统延迟&响应时间是指应用接受到一个请求到请求处理完成的时间。通常,降低响应时间的代价是使吞吐量降低,系统使用的内存也随之升高。
    内存占用
    内存占用是指在一定的系统需求下应用的jvm需要的内存分配量,对于java应用,通常主要指jvm堆的大小。
     启动时长
    启动时长是指应用在初始化时需要花费的时间。除了初始化时加载类、初始化需要的对象等,不同得jvm runtime环境也会影响到启动时长,相比之下server模式比client模式花费更长的启动时长。
    系统需求排序
    调优的第一步是把各方对系统的调优需求进行整理排序。最开始的调优是以最重要的调优需求来驱动的。比如可用性是系统的首要要求时,在jvm部署模型选择时就要选择多jvm实例的形式;而当可控性是系统的首要选择时,就需要选择单实例的jvm发布模型进行部署。
    选择部署模型
    jvm部署模型是在部署时用单个jvm实例部署还是多实例进行部署的两种方式的选择。
    单虚拟机部署模型的好处是当应用部署在一个虚拟机实例中时可以减少管理多个虚拟机和配置相关的花费。而且因为是只部署在一个应用当中,应用可以使用的总内存也会受jvm配置限制而相对较小。单虚拟机部署的一个风险是当应用出现异常导致应用级别的错误时,整个应用都将不能提供访问。
    多虚拟机部署应用的好处是可以增加可用性,在集群中部署应用时,当集群中一个jvm机器挂掉之后只会导致应用中部分的服务不可用。此外,多jvm集群部署也可以使单个jvm的堆内存变小,因为垃圾回收是影响响应延迟的很重要的指标,所以当堆内存变小时,可以比较明显得为系统需求中响应时间的需求带来提升。另外一个方面,在多个jvm中部署应用,可以把原本在一个jvm上的压力分摊到多个jvm上,可以降低单个jvm上的压力。
    此外,多虚拟机实例运行时,每个虚拟机的java线程会绑定在特定的cpu核上执行,相比较单个虚拟机的场景,减少了java线程在多个cpu核或者超线程上切换以及cpu缓存失效带来的性能损耗。
    多虚拟机部署带来的问题是可管理性上的下降,管理和维护多个jvm带来的代价升高。
    选择jvm运行时环境
    jvm运行时环境包括三种模式,纯client模式,纯server模式,client和server结合的模式。client模式的特点是可以让jvm更快速得启动,占用更少的内存以及使用更快速的编译器。server模式提供了更成熟的编译器,这种模式在客户端服务器上更适合使用。但是server模式的编译需要花费额外的一些时间来搜集代码的运行时情况,从而来编译生成更高效的机器代码。
    第三种模式是上面两种模式的结合,即tiered模式,在java6update25以上可以使用这种模式,它结合了client模式和server模式的优点,启动快,并且生成代码的效率也更高。用户可以使用-server -XX:TieredCompilation参数来指定。但是目前这种模式还不太成熟。关于两种模式的区别可参看:http://developer.51cto.com/art/201009/228035.htm。使用java -version可以查看jvm默认使用的模式。
    除了client模式和server模式的选择。还需要在32位虚拟机和64位虚拟机之间进行选择。32位虚拟机是HotSpot虚拟机默认的模式。
32位或者64位虚拟机的选择决定了虚拟机的内存使用。要是用64位jvm,有两个条件必须要保证:一是应用使用的第三方包必须要支持64位jvm,二是应用是否依赖了本地模块。如果使用64位虚拟机,应用依赖的本地模块必须以64位的模式进行编译。
     选择垃圾回收器
    HotSpot vm中,可供选择的垃圾回收方式有serial、throughput、concurrent、g1。
    当应用在使用throughput方式gc情况下需要在响应时间上有所提升时,应用可以考虑使用concurrent模式垃圾回收。使用throughput方式垃圾回收可以用两个参数-XX:+UseParallelOldGC或者-XX:+UseParallelGC。两者的不同点是-XX:+UseParallelOldGC设定了minor gc和major gc都使用多线程进行垃圾回收,而-XX:+UseParallelGC只设定了minor时使用多线程进行垃圾回收,在这个参数下,年老代的gc是单线程的。因为使用-XX:+UseParallelOldGC参数会自动设置-XX:+UseParallelGC,所以这种参数设置下,年轻代和年老代的gc都是多线程的。
   垃圾回收调优基础
    这节介绍三个垃圾回收导相关的性能指标,三个基础的垃圾回收策略,以及在调优垃圾回收过程中需要收集的数据。即调优时各性能指标、调优策略、调优时需要关心的数据。
     垃圾回收相关性能指标
    吞吐量:衡量应用在只考虑垃圾回收器工作的情况下,不考虑垃圾回收暂停时间、内存使用总数等因素,让应用达到峰值性能的能力。
    响应延迟:衡量垃圾回收器让应用表现出低延迟,暂停时间低的程序。
    内存占用量:衡量让垃圾回收器运行得足够高效时合适的jvm内存配置。
    对上述三者之一的性能指标的调优通常意味着消耗另外两种性能指标。不过另一方面,大多数的应用很少同时在三个方面都要求很高,通常应用只关注一到两个指标。
    更了解系统性能需求的优先级一样,了解垃圾回收影响的三个性能指标的优先级也同样非常重要。识别了三种性能指标中哪些是最重要的,其实也就能够知道哪些系统性能指标是应用更为关心的。
    下面介绍是 三个jvm垃圾回收调优的基础原则
    1.让minor gc最大程度得回收对象。坚持这条原则可以让应用减少fgc的次数和频率。fgc会给应用带来相当长的响应延迟,会导致应用在吞吐量和相应延迟两个指标上有所损失。
    2.java堆内存越大,垃圾回收对应用吞吐量和响应延迟上的表现就越好。
    3.只对“吞吐量”、“响应时间”、“内存占用量”三个指标中的两个进行调优才有可能完成。
     命令行参数和gc日志
   决定jvm调优方向的调优动作可以从观察垃圾回收的过程中得到,最好的观察方式是获取jvm的垃圾回收日志。这就意味着应用必须通过jvm运行参数打开gc日志功能,这点其实就算是在生产环境,也是十分必要的。gc日志在给运行时应用带来较小的开销的同时为应用维护人员提供了丰富的应用和jvm级别事件信息。比如,应用在某一时刻发生了一次很长的暂停(不对外提供响应),通过gc日志,应用维护人员就可以判断是否是由于gc的原因导致了这次暂停,亦或者是应用程序本身带来的。
    开启gc日志最基本的jvm参数是:-XX:+PrintGCTimeStamps -XX:+PrintGCDetails -Xloggc:。-XX:+PrintGCTimeStamps参数指定输出gc发生时离jvm启动的时间间隔。-XX:+PrintGCDetails 参数提供了和垃圾回收器关联的垃圾回收相关统计信息,如回收前堆大小,回收后堆大小等。-Xloggc:参数指定了gc日志输出的文件位置。
    另外一个查看GC相关的参数是-XX:PrintGCDateStamps,这个参数提供了gc发生时的本地时间。
    如果应用在响应时间上达不到需求,需要排查是否是因为jvm内部的SafePoint导致了线程不响应,从而导致响应时长变长,则可以用下面几个参数-XX:+PrintGCApplicationStoppedTime、-XX:+PrintGCAppliactionConcurrentTime、-XX:+PrintSafepointStatistics来查看。因为在jvm进入safepoint状态时,所有java线程被阻塞,堆内存不会变化。safepoint是在jvm需要进行一些内部操作的时候jvm必须要达到的状态。
    附: jvm Runtime部分的读书笔记:
    safepoints即为安全点,是vm在需要做一些操作的时候把运行的线程进入一个安全点的状态,这样才能做一些安全操作,最熟悉的安全点操作是FGC的支持,比如FGC时候的stop-the-world阶段。

另外一些safepoint比如:偏向锁撤销,即偏向锁在出现多线程竞争的时候,退回到轻量级锁、线程dump、线程挂起和暂停
线程会询问虚拟机是否要在safepoint暂停的场景是:线程状态切换、即时编译后的代码在一个循环阶段从一个方法返回或者特定的阶段。

你可能感兴趣的:(java,performance)