第4章 虚拟机性能监控、故障处理工具
4.2基础故障处理工具
4.2.1jps:虚拟机进程状况工具
jps:可以列出正在运行的虚拟机进程,并显示虚拟机执行主类名称以及这些进程的本地虚拟机唯一ID。
格式:jps [ options ] [ hostid ]
4.2.2jstat:虚拟机统计信息监视工具
jstat:用于监视虚拟机各种运行状态信息的命令行工具,可以显示本地或者远程虚拟机进程中的类加载、内存、垃圾收集、即时编译等运行时数据,常用于没有GUI图形界面、只提供了纯文本控制台环境的服务器上。
格式:jstat [ option vmid [interval[s|ms] [count]] ]
4.2.3jinfo:Java配置信息工具
Jinfo:实时查看和调整虚拟机各项参数
格式: jinfo [ option ] pid
4.2.4jmap:Java内存映像工具
jmap:用于生成堆转储快照。
格式: jmap [ option ] vmid
4.2.5jhat:虚拟机堆转储快照分析工具
与jmap搭配使用,来分析jmap生成的堆转储快照。jhat内置了一个微型的HTTP/Web服务器,生成堆转储快照的分析结果后,可以在浏览器中查看。
4.2.6jstack:Java堆栈跟踪工具
用于生成虚拟机当前时刻的线程快照。
jstack [ option ] vmid
4.3可视化故障处理工具
4.3.1JHSDB:基于服务性代理的调试工具
整合了所有基础工具所能提供的专项功能。
4.3.2JConsole:Java监视与管理控制台
不仅可以用在虚拟机本身的管理上,还可以运行于虚拟机之上的软件中。
-
启动JConsole 启动JConsole会自动搜索出本机运行的所有虚拟机进程,也可以远程;
-
内存监控 用于监视被收集器管理的虚拟机内存的变化趋势;
-
线程监控
相当于可视化的jstack命令。
4.3.3VisualVM:多合-故障处理工具
生成、浏览堆转储快照
4.3.4Java Mission Control:可持续在线的监控工具
有飞行记录器,可以生成报告
4.4HotSpot虚拟机插件及工具
HSDIS:JIT生成代码反汇编,可查看反编译后的代码。
第5章 调优案例分析与实战
5.2.1大内存硬件上的程序部署策略(jvm类问题)
案例描述:。一个15万PV/日左右的在线文档类型网站最近更换了硬件系统,服务器的硬件为四路志强处理器、16GB物理内存,操作系统为64位CentOS 5.4,Resin作为Web服务器。软件版本选用的是64位的JDK 5,管理员启用了一个虚拟机实例,使用-Xmx和-Xms参数将Java堆大小固定在12GB,网站经常不定期出现长时间失去响应。一次FullGC需要14s。访问文档时会把文档从磁盘提取到内存中,导致内存中出现很多由文档序列化产生的大对象,这些大对象大多在分配时就直接进入了老年代,没有在Minor GC中被清理掉。
解决办法:调整为建立5个32位JDK的逻辑集群,每个进程按2GB内存计算(其中堆固定为1.5GB),占用了10GB内存。建立一个Apache服务作为前端均衡代理作为访问门户。考虑到用户对响应速度比较关心,并且文档服务的主要压力集中在磁盘和内存访问,处理器资源敏感度较低,因此改为CMS收集器进行垃圾回收。
目前单体应用在较大内存的硬件上主要的部署方式有两种:
1)通过一个单独的Java虚拟机实例来管理大量的Java堆内存。
2)同时使用若干个Java虚拟机,建立逻辑集群来利用硬件资源。
5.2.2集群间同步导致的内存溢出(io类问题)
案例描述:一个基于B/S的MIS系统,硬件为两台双路处理器、8GB内存的HP小型机,应用中间件是WebLogic 9.2,每台机器启动了3个WebLogic实例,构成一个6个节点的亲合式集群。由于是亲合式集群,节点之间没有进行Session同步,但是有一些需求要实现部分数据在各个节点间共享。最开始这些数据是存放在数据库中的,但由于读写频繁、竞争很激烈,性能影响较大,后面使用JBossCache构建了一个全局缓存。全局缓存启用后,服务正常使用了一段较长的时间。但在最近不定期出现多次的内存溢出问题。
分析:MIS服务需要每当接收到请求时,均会更新一次最后操作时间,并且将这个时间同步到所有的节点中去,使得一个用户在一段时间内不能在多台机器上重复登录。在服务使用过程中,往往一个页面会产生数次乃至数十次的请求,因此这个过滤器导致集群各个节点之间网络交互非常频繁。当网络情况不能满足传输要求时,重发数据在内存中不断堆积,很快就产生了内存溢出。
5.2.3堆外内存导致的溢出错误(jvm类问题)
案例描述:基于B/S的电子考试系统,为了实现客户端能实时地从服务器端接收考试数据,系统使用了逆向AJAX技术(也称为Comet或者Server Side Push),选用CometD 1.1.1作为服务端推送框架,服务器是Jetty 7.1.4,硬件为一台很普通PC机,Core i5 CPU,4GB内存,运行32位Windows操作系统。测试期间发现服务端不定时抛出内存溢出异常,把堆内存调至最大仍没有效果。
分析:32位Windows平台的限制是2GB,其中划了1.6GB给Java堆,而Direct Memory只有0.4GB,虚拟机对直接内存只有在FullGC出现后一起回收。
5.2.4外部命令导致系统缓慢(cpu类问题)
案例描述:个数字校园应用系统,运行在一台四路处理器的Solaris 10操作系统上,中间件为GlassFish服务器。系统在做大并发压力测试的时候,发现请求响应时间比较慢,通过操作系统的mpstat工具发现处理器使用率很高,但是系统中占用绝大多数处理器资源的程序并不是该应用本身。
分析:执行外部shell脚本导致,Java虚拟机执行这个命令的过程是首先复制一个和当前虚拟机拥有一样环境变量的进程,再用这个新的进程去执行外部命令,最后再退出这个进程。
改为API获取信息。
5.2.5服务器虚拟机进程崩溃(JVM类问题)
案例描述:一个基于B/S的MIS系统,硬件为两台双路处理器、8GB内存的HP系统,服务器是WebLogic9.2。正常运行一段时间后,最近发现在运行期间频繁出现集群节点的虚拟机进程自动关闭的现象,留下了一个hs_err_pid###.log文件后,虚拟机进程就消失了,两台物理机器里的每个节点都出现过进程崩溃的现象。从系统日志中注意到,每个节点的虚拟机进程在崩溃之前,都发生过大量相同的异常。
分析:异步服务导致等待线程和Socket链接越来越多,最终超过虚拟机的承受能力后导致虚拟机进程崩溃,并将异步调用改为生产者/消费者模式的消息队列实现后,系统恢复正常。
5.2.6不恰当数据结构导致内存占用过大(jvm类问题)
案例描述:一个后台RPC服务器,使用64位Java虚拟机,内存配置为-Xms4g-Xmx8g-Xmn1g,使用ParNew加CMS的收集器组合。平时对外服务的Minor GC时间约在30毫秒以内,完全可以接受。但业务上需要每10分钟加载一个约80MB的数据文件到内存进行数据分析,这些数据会在内存中形成超过100万个HashMap<Long,Long>Entry,在这段时间里面Minor GC就会造成超过500毫秒的停顿。
分析:在分析数据文件期间,800MB的Eden 空间很快被填满引发垃圾收集,但Minor GC之后,新生代中绝大部分对象依然是存活的。我们知道ParNew收集器使用的是复制算法,这个算法的高效是建立在大部分对象都“朝生夕灭”的特性上的,如果存活对象过多,把这些对象复制到Survivor并维持这些对象引用的正确性就成为一个沉重的负担,因此导致垃圾收集的暂停时间明显变长。
解决:将survivor空间去掉,让新生代中的存活对象在第一次MinorGc后立即进入老年代;改变程序员有的HashMap<Long,Long>结构来存储数据文件空间,HashMap空间效率比较低。
5.2.7由Windows虚拟内存导致的长时间停顿(io类问题)
案例描述:有一个带心跳检测功能的GUI桌面程序,每15秒会发送一次心跳检测信号,如果对方30秒以内都没有信号返回,那就认为和对方程序的连接已经断开。程序上线后发现心跳检测有误报的可能,查询日志发现误报的原因是程序会偶尔出现间隔约一分钟的时间完全无日志输出,处于停顿状态。
分析:最小化的时候,资源管理中显示的占用内存大幅度减小,但是虚拟内存则没有变化,因此怀疑程序在最小化时它的工作内存被自动交换到磁盘的页面文件之中了,这样发生垃圾收集时就有可能因为恢复页面文件的操作导致不正常的垃圾收集停顿。
5.2.8由安全点导致长时间停顿(Jvm类问题)
案例分析:有一个比较大的承担公共计算任务的离线HBase集群,运行在JDK 8上,使用G1收集器。每天都有大量的MapReduce或Spark离线分析任务对其进行访问,同时有很多其他在线集群Replication过来的数据写入,因为集群读写压力较大,而离线分析任务对延迟又不会特别敏感,所以将-XX: MaxGCPauseMillis参数设置到了500毫秒。不过运行一段时间后发现垃圾收集的停顿经常达到3秒以上,而且实际垃圾收集器进行回收的动作就只占其中的几百毫秒。
分析:查看安全点日志-》查看超时的线程
使用int类型或范围更小的数据类型作为索引值的循环默认是不会被放置安全点的,但循环单次所耗时较多。
解决:把循环索引的数据类型从int改为long
5.3实战:Eclipse运行速度调优
5.3.1调优前的程序运行状态
5.3.2升级JDK版本的性能变化及兼容问题
Jdk5有设置永久代最大容量的启动参数。
5.3.3编译时间和类加载时间的优化
编译时间是指虚拟机的即时编译器编译热点代码的耗时,如果一段Java方法被调用次数到达一定程度,就会被判定为热代码交给即时编译器即时编译为本地代码,提高运行速度。
5.3.4调整内存设置控制垃圾收集频率
Minor GC耗时不到1s,发生了378次,是由新生代空间过小所致。Full GC由老年代自动扩容导致,要固定老年代容量。
5.3.5选择收集器降低延迟
Cpu使用率低,使用并发收集器提高效率。
第三章 深入理解JVM
3.1Java代码的执行机制
3.1.1Java源码编译机制
添加图片注释,不超过 140 字(可选)
-
分析和输入到符号表 词法分析(代码装换成token序列)+语法分析(token序列生成抽象语法树)
-
注释处理 处理自定义annotation
-
语义分析和生成class文件 对语法树进行语义分析。 生成class文件:将示例成员初始化到收集器,静态成员变量收集为<clinit>();将抽象语法树生成字节码,后续遍历语法树,代码转换;从符号表生成class文件; Class文件包含了:结构信息、元数据、方法信息。
-
类加载机制
分为三个步骤:装载、链接、初始化。
添加图片注释,不超过 140 字(可选)
-
装载:通过全类名+类加载器加载。对于接口或非数组型的类,其名称为类名,次种类型由所在的ClassLoader负责加载;数组型类中的元素由所在ClassLoader负责加载,但数组类由JVM直接创建。
-
链接:对二进制字节码的格式进行校验、初始化装载类中的静态变量及解析类中调用的接口、类。
-
初始化:调用了new、反射类调用了类中的方法、子类调用了初始化、JVM启动过程中指定的初始化类,会触发初始化。
-
类的执行机制
字节码解释执行:
-
指令解释执行:获取下一条指令,解码并分派,执行。
-
栈顶缓存:将本来唯一操作数栈顶的值直接缓存再寄存器上。
-
部分栈帧共享:当一个方法调用另一个方法时,后一方法可将前一方法的操作数栈作为当前方法的局部变量,节省copy的消耗。
编译执行:
Sun JDK提供了两种模式
C1:
-
方法内联 C1采取方法内联的方式,即把调用到的方法的指令植入当前方法中。
添加图片注释,不超过 140 字(可选)
-
去虚拟化 类的方法只有一个实现类,调用此方法的代码也可以进行方法内联。
-
冗余剔除
在编译时,根据运行时情况进行代码折叠或剔除
添加图片注释,不超过 140 字(可选)
C2
-
标量替换 用标量替换聚合量,如果创建的对象为用到其中的全部变量可以节省一部分空间。
添加图片注释,不超过 140 字(可选)
-
栈上分配 对象未逃逸,在栈上直接创建对象实例,更加迅速,可以随着方法结束将对象一起回收。
-
同步剔除 同步的对象未逃逸,会直接去掉同步。 反射执行 反射可动态调用某对象实例中对应的方法,访问查看对象的属性。
-
JVM内存管理
-
内存空间
分为方法区、堆、本地方法栈、pc寄存器、JVM方法栈。
添加图片注释,不超过 140 字(可选)
方法区:存放要加载的类的信息、类中的静态变量、定义为final类型的常量、Filed信息、方法信息,全局共享。这块区域对应持久代。
堆:存放对象及数组值。
-
新生代 大多数情况Java程序中新建的对象从新生代分配,新生代由Eden Space和两块大小相同的的Survivor Space(from+to)构成。
-
旧生代
存放新生代经过多轮回收仍存活的对象(大对象可直接在旧生代分配)。
本地方法栈
支持native方法的执行
PC寄存器和JVM方法栈
每个线程均会创建PC寄存器和JVM方法栈,jvm方法栈线程私有。
-
内存分配 为了提高内存分配效率,会在eden区上分配一块TLAB,在TLAB上分配不需要加锁,jvm分配空间时会尽量在TLAB上分配,如果对象过大或者TLAB已用完,仍在堆上分配。
-
内存回收
收集器
-
引用计数收集器 计数器为0可被回收
-
跟踪收集器
全局记录数据的引用状态,执行时需要从根节点集合扫描对象的引用关系,主要有复制、标记-清除、标记-压缩。
复制:找到存活对象复制到一块新的完全未使用的空间
标记清除:从根节点扫描,对存活对象标记,标记完在扫描整个未标记对象进行回收。
标记压缩:标记完会将所有存活对象往左端空闲的空间移动,并更新引用指针。
Sun JDK中可用的GC
添加图片注释,不超过 140 字(可选)
新生代可用GC(Minor GC)
-
串行GC(Serial GC) 新生代分配使用空闲指针的方式,指针在最后一个分配的对象在新生代的位置。剩余不够分配则Minor GC。对于Minor GC,根节点为Sun JDK认为的跟集合加上remember set(记录旧生代引用新生代的情况)中标记的对象。比了避免扫描中出现引用变化,在编译时给方法加入SafePoint(通常位于循环结束的点及方法执行完毕的点),暂停应用时需要等待用户线程进入SafePoint。 对象引用关系 强引用: A a=new A();主动释放才会被GC。 软引用:用SoftReference实现,当JVM内存不足时会被回收,适合缓存。 弱引用:用WeakReference实现,采用弱引用建立引用的对象没有强引用后GC时会被自动释放。 虚引用:采用PhantomRefenence实现,可跟踪到对象时候已从内存中被删除。 扫描到存活对象时,复制到S0或S1中,当再进行Minor GC时 复制到S1或S0中,经历过几次Minor GC才会放入旧生代。 Serial GC单线程执行,适用于单cpu、新生代较小、对停顿时间要求不高的应用上。
-
并行回收GC(Parallel Scavenge) 会动态调整Eden,s0,s1的大小,Eden区不够时,对象大于eden的一般直接在旧生代分配。使用copy算法,扫描和复制多线程执行,适合多cpu、对停顿时间要求较短的应用。
-
并行GC(ParNew)
与并行回收区别在于并行回收使用CMS GC(在旧生代GC有时是并发执行的需要相应处理),并行回收不需要。
Minor GC示例
MinorGC时survivor空间不足 对象直接进入旧生代。
旧生代和持久代可用的GC
-
串行 从根节点按照三色着色对对象进行标识。 遍历整个旧生代空间找出未标识的对象,回收其内存。 将存活对象向旧生代空间开始处滑动。
-
并行 将代空间划分为并行线程个数的区域,然后根据集合可访问到的对象及并行线程数进行划分,并对这些对象进行三色着色。 通常旧生代左边放的是活跃对象。找到第一个值得回收的region,其左边的不进行回收,对右边regions压缩,切换引用这些对象的指针,并标记。 找到没有存活对象的region并行进行对象移动和region回收
-
并发(CMS)
CMS采用 free list记录旧生代哪些部分是空闲的。
CMS扫描、着色:
-
第一次标记 暂停整个应用,扫描从跟集合到旧生代可直接访问的对象,对这些着色,并采用外部bit数组记录
-
并发标记 恢复所有线程,轮询着色对象,标记这些对象可访问的对象。旧生代对象引用关系被改变用Card Table记录,并标识为dirty状态。
-
重新标记 暂停整个应用,并发标记可能改变对象引用关系或创建新的对象,要对这些对象扫描,重新着色。
-
并发收集
恢复所有线程,将未标记对象回收。
Full GC
触发Full GC:旧生代空间不足、永久代(存放一些class信息)空间满、CMS GC出现promotion failed和 concurrent mode failure、统计得到的Minor GC晋升到旧生代的平均大小大于旧生代的剩余空间。
添加图片注释,不超过 140 字(可选)
添加图片注释,不超过 140 字(可选)
Sun JDK提供两种简单方式帮助选择GC
-
吞吐量优先
-
暂停时间优先
Garbage First(G1)
尽可能减少GC导致的暂停时间,同时保持堆的利用率。G1将堆划分为多个固定大小的region,扫描时并发marking算法对整个堆的region进行标记,回收根据region中活跃对象的大小进行排序,首先回收活跃对象小及耗时短的region。回收时将region中的活跃对象复制到另外的region中,根据GC时间估算能回收多少region。Region之间的对象引用通过remembered set来维护,用Card Table来应用通知region修改remembered set。
-
Initial Marking G1对每个region保存了两个bitmap,来指向对象的起始点,开始Initial Marking 前首先并发清空 next marking bitmap,停止所有应用线程,扫描每个region中root可直接访问到的对象,将region中top的值放入TAMS中,恢复线程。
-
Concurrent Marking 按照Initial Marking扫描到的对象进行遍历,识别下层对象,在此期间被修改的对象记录到remember set logs中
-
Final Marking Pause 对remember set logs存在的内容进行处理,修改remember sets
-
Live Data Counting and Cleanup
G1为了到达实时要求,需要根据用户指定的最大的GC造成的暂停时间来规划什么时候执行Cleanup。
-
JVM内存状况查看方法和分析工具
-
输出GC日志 人为分析
-
GC Portal 分析GC日志,并生成相关图形化报表
-
JConsole 图形化查看JVM内存情况
-
JVisulaVM 可查看内存消耗情况、线程执行情况及程序中消耗CPU、内存的动作
-
JMap 可查看JVM中各个代的内存情况、对象的内存占用情况以及导出整个JVM中内存信息。
-
JHat 可分析jvm heap中对象的内存占用情况、引用关系。
-
JStat 分析GC的状况、编译情况、class加载的状况
-
Eclipse Memory Analyzer 可查看内存占用情况、引用关系、分析内存泄漏
-
JVM线程资源同步及交互机制
-
线程资源同步机制
Int i=0;
Public int getNextId(){
return i++;
}
JVM首先在main memory给i分配一个内存存储场所,并存储其值0;
线程启动后,自动分配一片 working memory区,执行到return i++动作分为装载i、读取i、进行i+1操作、存储i及写入i。
装载i:线程向jvm线程引擎发送请求,接收到引擎后会向main memory发起一个 read i的指令,执行完毕将i从main memory复制到working memory。
读取i:从main memory读取i
进行i+1操作:由线程完成
存储i:将i+1赋值给i,存储到working memory。
写入i:一段时间后将i写回main memory。
JVM保证一下操作是顺序进行的:
-
同一线程的操作
-
Main memory上的同一变量的操作
-
加了锁的main memory Synchronized:保证操作原子性。
-
线程交互机制
添加图片注释,不超过 140 字(可选)
调用Object的wait方法可以让当前线程进入等待状态,只有其他线程调用此Object的notify或notifyAll方法,或wait到达了指定的时间后才会激活执行。Notify随机找wait此object的一个线程,而notifyAll则是通知wait此Object的所有线程。
-
线程状态及分析
添加图片注释,不超过 140 字(可选)
Kill -3[pid]:输出线程相关信息
Jstack:可以直接看到jvm中线程的运行情况,包括锁的等待、线程是否在运行。
JConsole:图形化跟踪查看运行系统中的线程情况。
ThreadXMBean:可直接访问MBean,可获取jvm所有线程、线程运行状态、每个线程耗费的cpu时间、处于等待Object锁的线程数。
TDA:分析线程堆栈信息的图形化工具。
TOP命令+ThreadDump:分析目前线程消耗CPU的状况。
第五章 性能调优
添加图片注释,不超过 140 字(可选)
5.1寻找性能瓶颈
5.1.1 CPU消耗分析
上下文切换
Linux采用抢占式调度。为每个线程分配一定的执行时间,当到时间、线程中由IO阻塞、高优先级线程要执行时,Linux将切换执行的线程,保存当前线程的状态。在文件IO、网络IO、锁等待、线程sleep时当前线程会进入阻塞、休眠状态,从而触发上下文切换。
运行队列
每个cpu核都维护了一个可运行的线程队列。
利用率
-
top top命令可查看CPU消耗情况
-
pidstat
可查看每个线程的具体CPU利用率。
添加图片注释,不超过 140 字(可选)
Cpu消耗严重主要体现在us、sy两个值上。
-
us us过高表示运行的应用消耗了大部分CPU,要找到具体消耗CPU线程所执行的代码。通过linux命令找到CPU消耗严重的线程及其ID,之后通过kill -3 [javapid]或jstack的方式dump出应用的java线程信息。 Us搞主要是线程一直在可运行状态,通常是线程在执行无阻塞、循环、正则、纯粹计算造成,或频繁GC。
-
sy
sy过高表示linux话费更多时间进行线程切换,主要是因为线程较多,多数处于不断阻塞和执行状态的变化中。Kill -3[javapid]或jstack -l [javapid]的方式dump出java应用程序的线程信息,锁信息,找出等待状态或锁竞争过多的线程。
5.1.2 文件IO消耗分析
Linux在操作文件时。将数据放入文件缓存区,知道内存不够或系统要释放内存给用户进程使用。Pidstat可跟踪文件io消耗。
Pidstat
Pidstat -d -t -p[pid] 1 100
添加图片注释,不超过 140 字(可选)
iostat
可查看各个设备的IO历史情况
添加图片注释,不超过 140 字(可选)
Java应用造成文件IO消耗验证主要是多个线程进行大量写入或磁盘本身的处理速度慢或文件系统慢火操作的文件本身很大。
5.1.3 网络IO消耗分析
Linux中可采用sar来分析网络IO的消耗情况 sar -n full 1 2
5.1.4 内存消耗分析
对于内存的消耗主要是在JVM堆内存上,将-Xms和-Xmx设置为相同的值,避免频繁申请内存。除了堆消耗内存外,还有swap和物理内存消耗。可通过vmstat、sar、top、pidstat来查看。
对物理内存的消耗
基于Direct ByteBuffer可以很容易实现对物理内存的直接操作,无需消耗JVM heap区。
对JVM内存的消耗
Java程序出现内存消耗过多、GC频繁、OutOfMemoryError时,先分析消耗的是物理内存还是JVM heap区。若为物理内存分析线程及Direcr ByteBuffer的使用情况;如为heap区,使用工具分析具体对象的内存占用。
5.1.5程序执行慢原因分析
1.锁竞争激烈
2.未充分使用硬件资源
3.数据量增长
5.2 调优
5.2.1 JVM调优
代大小的调优
关键参数:-Xms -Xmx -Xmn -XX:SurvivorRatio -XX:MaxTenuringThreshold(经历多少粗Minor GC转入旧生代)
-
避免新生代大小设置过小 设置过小会导致minor GC次数频繁;minor gc对象直接进入旧生代,触发full gc。当minor GC过于频繁,或经常出现minor gc时 survivor的一个区域满且old gen增长超过了survivor区域大小时,考虑调整新生代大小,原则是在不调大JVM Heap的情况下放大新生代空间,让对象在minor gc阶段被回收;在能调大heap时,按照新生代空间大小增加heap大小,保证旧生代够用。
-
避免新生代设置过大 导致旧生代变小;minor gc的耗时大幅度增加。通常新生代占heap区的33%左右。
-
避免Survivor区过小或者过大 无法调整heap以及新生代的大小时,合理调整Survivor区的大小也有一定效果。调大SurvivorRatio以为Eden区变大,minor gc触发次数降低,但survivor区变小,如有超过survivor大小的空间在minor gc后没有被回收则直接进入旧生代。调小SurvivorRatio意味着eden区变小,minor gc触发次数增加,survivor区域变大可以存储更多的对象避免进入旧生代。
-
合理设置新生代存活周期
默认是15次,需要根据应用调优。
GC策略调优
JVM调优案例
在进行参数调整时,可根据目前收集到的顶峰时的系统请求次数,响应时间及GC的信息估计系统每次请求需要消耗的内存,以及每次Minor gc时存活对象所占的内存,从而估计设置多大的survivor才尽可能少让对象进入旧生代。
5.2.2 程序调优
CPU消耗严重的解决办法
-
cpu us高的解决办法 主要原因是线程无任何挂起动作,执行时导致cpu没有机会去调度其他线程。-》增加Thread.sleep释放cpu的执行权。 场景扫描,例如某线程要等其他线程改变了值后才能继续执行-》改为采用wait/notify机制。
-
CPU sy高的解决方法
主要原因是线程要经常切换-》减少线程数。
锁竞争激烈-》降低线程间的锁竞争。
较多网络IO或确实需要锁竞争机制-》采用协程来支撑高并发量
文件IO消耗严重的解决方法
主要原因是多个线程在写大量的数据到同一文件,导致文件很快变得很大。
常用调优方法:
异步写文件
批量读写
限流
限制文件大小
网络IO消耗严重的解决方法
主要原因是同时需要发送或接受的包太多-》限流,限制发送packet的频率
对于内存消耗严重的情况
主要是消耗过多的堆内存,造成频繁gc。
-
释放不必要的引用
-
使用对象缓存池 避免创建对象所耗费的时间及频繁GC造成的消耗。
-
采用合理的缓存失效算法
-
合理使用SoftRefenence和WeakReference SoftRefenence的对象在内存不够用的时候回收,WeakReference在Full GC的时候回收。可减少Heap区内存消耗。
5.2.3 对于资源消耗不多,但程序执行慢的情况
主要原因是锁竞争激烈、未发挥硬件资源两种。
锁竞争激烈
-
使用并发包中的类
-
使用Treiber算法
-
使用Michael-Scott非阻塞队列算法
-
尽可能少用锁 尽可能让锁最小化,只对互斥及原子操作的地方加锁
-
拆分锁
-
去除读写操作的互斥锁
未充分使用硬件资源
未充分使用cpu,在cpu资源消耗可接受且不会因为线程增加带来激烈锁竞争的场景下应对处理过程分解,增加线程数提高性能。
未充分使用内存
如数据的缓存,耗时资源的缓存,页面片段的缓存。充分使用内存来缓存数据,提升系统性能。
Cpu类问题:
cpu us高的解决办法
主要原因是线程无任何挂起动作,执行时导致cpu没有机会去调度其他线程。-》增加Thread.sleep释放cpu的执行权。
场景扫描,例如某线程要等其他线程改变了值后才能继续执行-》改为采用wait/notify机制。
CPU sy高的解决方法
主要原因是线程要经常切换-》减少线程数。
锁竞争激烈-》降低线程间的锁竞争。
较多网络IO或确实需要锁竞争机制-》采用协程来支撑高并发量
IO类问题:
文件IO消耗严重的解决方法
主要原因是多个线程在写大量的数据到同一文件,导致文件很快变得很大。
常用调优方法:
异步写文件
批量读写
限流
限制文件大小
网络IO消耗严重的解决方法
主要原因是同时需要发送或接受的包太多-》限流,限制发送packet的频率
对于内存消耗严重的情况
主要是消耗过多的堆内存,造成频繁gc。
释放不必要的引用
使用对象缓存池 避免创建对象所耗费的时间及频繁GC造成的消耗。
采用合理的缓存失效算法
合理使用SoftRefenence和WeakReference SoftRefenence的对象在内存不够用的时候回收,WeakReference在Full GC的时候回收。可减少Heap区内存消耗。
JVM类问题:
关键参数:-Xms -Xmx -Xmn -XX:SurvivorRatio -XX:MaxTenuringThreshold(经历多少粗Minor GC转入旧生代)
-
避免新生代大小设置过小 设置过小会导致minor GC次数频繁;minor gc对象直接进入旧生代,触发full gc。当minor GC过于频繁,或经常出现minor gc时 survivor的一个区域满且old gen增长超过了survivor区域大小时,考虑调整新生代大小,原则是在不调大JVM Heap的情况下放大新生代空间,让对象在minor gc阶段被回收;在能调大heap时,按照新生代空间大小增加heap大小,保证旧生代够用。
-
避免新生代设置过大 导致旧生代变小;minor gc的耗时大幅度增加。通常新生代占heap区的33%左右。
-
避免Survivor区过小或者过大 无法调整heap以及新生代的大小时,合理调整Survivor区的大小也有一定效果。调大SurvivorRatio以为Eden区变大,minor gc触发次数降低,但survivor区变小,如有超过survivor大小的空间在minor gc后没有被回收则直接进入旧生代。调小SurvivorRatio意味着eden区变小,minor gc触发次数增加,survivor区域变大可以存储更多的对象避免进入旧生代。
-
合理设置新生代存活周期
默认是15次,需要根据应用调优。
GC策略调优
JVM调优案例
在进行参数调整时,可根据目前收集到的顶峰时的系统请求次数,响应时间及GC的信息估计系统每次请求需要消耗的内存,以及每次Minor gc时存活对象所占的内存,从而估计设置多大的survivor才尽可能少让对象进入旧生代。