前言
java是一门内存垃圾自动回收语言,大部分时候我们都是通过new关键字来创建一个java对象,然而我们并不知道它在底层是如何申请内存和释放内存,归根结底这个强大的功能是由jvm来完成。对于新手经常出现这样的问题,刚开始开发一个项目时接口响应时间很快感觉没有任何问题,但是上线一段时间后,发现程序的响应时间越来越慢,甚至有时出现卡顿现象,通俗的讲这样的问题就是性能调优的问题。为了更好的理解本文采用问答的方式进行讨论。
性能调优难吗?
答: 难,因为性能调优是综合解决方案,主要体现在:(1)代码优劣问题,(2)业务逻辑是否有待优化,(3)架构是否存在性能问题、(4)高并发下数据库是否已实现三高架构,(4)是否合理使用缓存,(5)是否做了限流熔断,(6)是否做了JVM性能调优等很多问题。所有性能调优一般是架构师负责的。本主要讨论JVM性能调优。
jvm调优需要懂那些知识?
jvm调优主要是垃圾回收的调优,所以在调优之前必须知道什么是垃圾?如何判定是垃圾?jvm的内存模型?jvm有那些垃圾回收算法?jvm有那些垃圾回收器?等知识点。
什么是垃圾?
当前创建一个对象用完之后,没有任何地方引用,则这个对象就是垃圾对象。
如何判定是垃圾?
jvm采用引用计数算法(无法解决循环依赖问题)和可达性分析算法来判断某一个对象是否是垃圾(Hostport 采用这种算法),更多细节请阅读《垃圾回收算法》
jvm的内存模型?
jvm将内存分为线程私有和线程共享两大类,其中线程私有分为java虚拟机栈,本地方法栈,程序计数器它们的生命周期和线程一致,另一部分是java堆,方法区(主要用来存放,一些元数据,常量池,静态常量等固定不变的数据),所以java堆才是我们主要调优的部分。对jvm内存模型不是很了解请阅读《java虚拟机内存模型》。
jvm有那些垃圾回收算法?
目前为止常见的jvm垃圾回收算法有,标记清除算法,标记复制算法,标记整理算法,各种自己的优缺点。更多细节请阅读《垃圾回收算法》
jvm有那些垃圾回收器?
jdk从1.0到现在经过了20几年的发展,研发了很多种垃圾回收器,一个比一个先进也更复杂
名称 | 搭配 | 作用 | 作用区域 |
---|
Serial | Serial Old 、ParNew Old、CMS | 单线程采用标记清除算法 | 年轻代 | Serial Old | ParNew | 单线程采用标记整理算法 | 老年代 | ParNew | ParNew Old、Serial Old 、CMS | 多线程版的 Serial | 年轻代 | ParNew Old | ParNew、Serial | 多线程版的 Serial Old | 老年代 | Parallel Scavenge | Parallel Old 、Serial Old | 并行方式垃圾回收,主要关注吞吐量 | 年轻代 | Parallel Old | Parallel Scavenge | 并行方式垃圾回收 | 老年代 | CMS | ParNew、 Serial | 并发标记算法,主要关注停顿时间 | 老年代 | G1 | – | JDK9后才出现,将堆内存分为多个Regoion区域的全新内存模型 | – | ZGC | – | 这个最强的垃圾回收器,在JDK16后可以使用 | – |
找出性能问题
本文主要讲解在linux环境下调优,假设当前系统只运行一个java程序。先确认出现问题的原因,则查看硬件资源,使用top命令查看CPU和内存的使用情况。
- 1、内存已满,需要添加内存,或对项目进行负载均衡
- 2、内存空闲还很多
- 使用 jps -lmv 查看java进程pid
- 使用 jmap -heap pid 当前java程序的对内存情况
- 分析当前是发生什么Minor GC 和 Full GC 次数和时间
3.1 发生Full GC次数频繁,可能出现原因:(1)出现内存泄漏,(2)Survivor 区域内存太小,当发生YGC时存活对象直接从End区进入老年区,(3)老年代old内存太小。出现(1)时说明有对象没有被回收它的数量会递增使用jmap -histo pid 数量大或占用空间大的对象,然后到程序代码中去看修改代码;出现(2)时将 Survivor 大小比例调大,保证大部分的朝生夕灭的垃圾在年轻代就已回收;出现(3)情况时应该将整个堆大小增大,保证老年代有足够的空间。 3.2 Minor GC过于频繁时适当调整新生代的大小(由于GC时间和内存大小成反比关系,所以年轻代的大小需要根据自己的业务找到这个平衡点)
- 使用arthas (arthas 是阿里巴巴的一个开源工具)thread -n 10 查看当前程序进程使用CPU最高的前10个线程。
- 如果这些线程大部分是GC线程说明当前是频繁GC状态,需要根据第二步来适当调整堆内存大小,减少GC的次数。
- 如果是程序的业务线程,thread -b 查看线程的阻塞情况,最后到代码中确认是当前操作IO型还是计算密集型,该升配置的还是要升
相关命令和参数
jdk自动工具
- jsp 列出正在运行的虚拟机进程
- jstat 监视虚拟机运行状态信息
- jmap 查询对信息和生成堆存储快照
- jstack 生成虚拟机当前时刻的线程快照,帮助定位线程出现长时间停顿的原因 (这个一般用arthas工具替代)
设定堆内存大小
- -Xms:启动JVM时的堆内存空间。
- -Xmx:堆内存最大限制。
- -Xmn:设置年轻代大小整个堆大小=年轻代大小 + 年老代大小 + 持久代大小,Sun官方推荐配置为整个堆的3/8
- -XX:PermSize=128M设置持久代大小
- -XX:MaxPermSize=128M设置持久代最大值,此值可以设置与-XX:PermSize相同,防止持久代内存伸缩,持久代设置很重要,一般预留其使用空间的1/3.
设定新生代大小
- -XX:NewRatio:新生代和老年代的占比。
- -XX:NewSize:新生代空间。
- -XX:SurvivorRatio:伊甸园空间和幸存者空间的占比。
- -XX:MaxTenuringThreshold:对象进入老年代的年龄阈值。
设定垃圾回收器
- -XX:+UseSerialGC 开启串行收集器
- -XX:+UseParallelGC 开启年轻代并行收集器,JDK5.0以上,JVM会根据系统配置自行设置,所以无需再设置此值
- -XX:+UseParallelOldGC开启老年代并行收集器
- -XX:+UseConcMarkSweepGC开启老年代并发收集器(简称CMS),可以和UseParallelGC一起使用
- -XX:CMSInitiatingOccupancyFraction=70老年代内存使用比例到多少激活CMS收集器,这个数值的设置有很大技巧基本上满足(Xmx-Xmn)*(100-CMSInitiatingOccupancyFraction)/100>=Xmn否则会出现“Concurrent Mode Failure”,promotionfailed,官方建议数值为68
- -XX:+UseCMSCompactAtFullCollection:使用并发收集器时,开启对年老代的压缩
其他常用参数
- -Xss: 设置每个线程的堆栈大小,设置每个线程的堆栈大小。JDK5.0以后每个线程堆栈大小为1M,以前每个线程堆栈大小为256K。更具应用的线程所需内存大小进行调整。在相同物理内存下,减小这个值能生成更多的线程。但是操作系统对一个进程内的线程数还是有限制的,不能无限生成,经验值在3000~5000左右,这个参数对性能的影响比较大的
- -XX:MaxTenuringThreshold=0:设置垃圾最大年龄。如果设置为0的话,则年轻代对象不经过Survivor区,直接进入年老代。对于年老代比较多的应用,可以提高效率。如果将此值设置为一个较大值,则年轻代对象会在Survivor区进行多次复制,这样可以增加对象再年轻代的存活时间,增加在年轻代即被回收的概论,linux64的java6默认值是15
- -XX:ParallelGCThreads=设置并行垃圾回收的线程数。此值可以设置与机器处理器数量相等(逻辑cpu数),这个不确定是物理、还是逻辑使用默认就好
- -XX:MaxGCPauseMillis=指定垃圾回收时的最长暂停时间,单位毫秒,如果指定了此值的话,堆大小和垃圾回收相关参数会进行调整以达到指定值,设定此值可能会减少应用的吞吐量
- -XX:GCTimeRatio=设定吞吐量为垃圾回收时间与非垃圾回收时间的比值,公式为1/(1+N)。例如,-XX:GCTimeRatio=19时,表示5%的时间用于垃圾回收。默认情况为99,即1%的时间用于垃圾回收
- -XX:+UseAdaptiveSizePolicy:设置此选项后,并行收集器会自动选择年轻代区大小和相应的Survivor区比例,以达到目标系统规定的最低相应时间或者收集频率等,此值建议使用并行收集器时,一直打开
- -XX:+DisableExplicitGC:禁止 java 程序中的 full gc, 如System.gc() 的调用. 最好加上, 防止程序在代码里误用了对性能造成冲击
- -XX:+PrintGCDetails 打应垃圾收集的情况
- -XX:+PrintGCTimeStamps 打应垃圾收集的情况
- -XX:+PrintGCApplicationConcurrentTime:打印每次垃圾回收前,程序未中断的执行时间。可与上面混合使用
- -XX:+PrintGCApplicationStoppedTime 打应垃圾收集时 , 系统的停顿时间
- -XX:+PrintGC 打印GC情况
- -XX:PrintHeapAtGC:打印GC前后的详细堆栈信息
总结
性能调优是一门很需要技术的工作,同一个程序不同环境调优方式也有可能不同,所以性能调优不仅要知道原理还需要和丰富的经验相结合
|