【转载】深入理解JVM性能调优
性能问题无非就这么几种:CPU、内存、磁盘IO、网络。那我们来逐一介绍以下相关的现象和一些可能出现的问题。
一、CPU过高。
查看CPU最简单的我们使用任务管理器查看,如下图所示,windows下使用任务管理器查看,Linux下使用top查看。
一般我们的服务器都采用Linux,因此我们重点关注一下Linux(注:windows模式下相信大家已经很熟悉了,并且前面我们已经提到,使用资源监视器可以很清楚的看到系统的各项参数,在这里我就不多做介绍了)
在top视图下,对于多核的CPU,显示的CPU资源有可能超过100%,因为这里显示的是所有CPU占用百分百的总和,如果你需要看单个CPU的占用情况,直接按键1就可以看到。如下图所示,我的一台测试机为8核16GB内存。
在
top 视图下,按键 shift+h 后,会显示各个线程的 CPU 资源消耗情况,如下图所示:
我们也可以通过
sysstat 工具集的 pidstat 来查看
注:sysstat下载地址:http://sebastien.godard.pagesperso-orange.fr/download.html
安装方法:
1、chmod +x configure
2、./configure
3、make
4、make install
如输入pidstat 1 2就会隔一秒在控制台输出一次当然CPU的情况,共输出2次
除了
top 、 pidstat 以外, vmstat 也可以进行采样分析
相关
top 、 pidstat 、 mstat 的用法大家可以去网上查找。
下面我们主要来介绍以下当出现CPU过高的时候,或者CPU不正常的时候,我们该如何去处理?
CPU消耗过高主要分为用户进程占用CPU过高和内核进程占用CPU过高(在Linux下top视图下us指的是用户进程,而sy是指内核进程),我们来看一个案例:
程序运行前,系统运行平稳,其中蓝色的线表示总的
CPU 利用率,而红色的线条表示内核使用率。部署 war 测试程序,运行如下图所示:
对于一个
web 程序,还没有任何请求就占用这么多 CPU 资源,显然是不正常的。并且我们看到,不是系统内核占用的大量 CPU ,而是系统进程,那是哪一个进程的呢?我们来看一下。
很明显是我们的
java 进程,那是那个地方导致的呢?这就需要用到我们之前提到的性能监控工具。在此我们使用可视化监控工具 VisualVM。
首先我们排除了是
GC 过于频繁而导致大 CPU 过高,因为很明显监控视图上没有 GC 的活动。然后我们打开 profilter 去查看以下,是那个线程导致了 CPU 的过高?
前面一些线程都是容器使用的,而下面一个线程也一直在执行,那是什么地方调用的呢?查找代码中使用
ThredPoolExecutor 的地方。终于发现以下代码。
private BlockingQueue queue;
private Executor executor;
//……
public void run() {
while(true){
try {
SendMsg sendMsg = queue.poll();//从队列中取出
if(null != sendMsg) {
sendForQueue(sendMsg);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
问题很显然了,我们看一下对应BlockingQueue的poll方法的API文档。
不难理解了,虽然使用了阻塞的队列,但是使用了非阻塞的取法,当数据为空时直接返回
null ,那这个语句就等价于下面的语句。
@Override
public void run() {
while(true){
}
}
相当于死循环么,很显然是非常耗费CPU资源的,并且我们还可以发现这样的死循环是耗费的单颗CPU资源,因此可以解释上图为啥有一颗CPU占用特别高。我们来看一下部署在Linux下的top视图。
猛一看,不是很高么?我们按键
1 来看每个单独 CPU 的情况!
这下看的很清楚了吧!明显一颗
CPU 被跑满了。(因为一个单独的死循环只能用到一颗 CPU ,都是单线程运行的)。
问题找到,马上修复代码为阻塞时存取,如下所示:
@Override
public void run() {
while(true){
try {
SendMsg sendMsg = queue.take();//从队列中取出
sendForQueue(sendMsg);
} catch (Exception e) {
e.printStackTrace();
}
}
}
再来监控
CPU 的变换,我们可以看到,基本上不消耗 CPU 资源(是我没做任何的访问哦,有用户建立线程就会消耗)。
再来看
java 进程的消耗,基本上不消耗 CPU 资源
再来看VisualVM的监控,我们就可以看到基本上都是容器的一些线程了
以上示例展示了
CPU 消耗过高情况下用户线程占用特别高的情况。也就是 Linux 下 top 视图中 us 比较高的情况。发生这种情况的原因主要有以下几种:程序不停的在执行无阻塞的循环、正则或者纯粹的数学运算、 GC 特别频繁。
CPU过高还有一种情况是内核占用CPU很高。我们来看另外一个示例。
package com.yhj.jvm.monitor.cpu.sy;
import java.util.Random;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* @Described:系统内核占用CPU过高测试用例
* @author YHJ create at 2012-3-28 下午05:27:33
* @FileNmae com.yhj.jvm.monitor.cpu.sy.SY_Hign_TestCase.java
*/
public class SY_Hign_TestCase {
private final static int LOCK_COUNT = 1000;
//默认初始化LOCK_COUNT个锁对象
private Object [] locks = new Object[LOCK_COUNT];
private Random random = new Random();
//构造时初始化对应的锁对象
public SY_Hign_TestCase() {
for(int i=0;i<LOCK_COUNT;++i)
locks[i]=new Object();
}
abstract class Task implements Runnable{
protected Object lock;
public Task(int index) {
this.lock= locks[index];
}
@Override
public void run() {
while(true){ //循环执行自己要做的事情
doSth();
}
}
//做类自己要做的事情
public abstract void doSth();
}
//任务A 休眠自己的锁
class TaskA extends Task{
public TaskA(int index) {
super(index);
}
@Override
public void doSth() {
synchronized (lock) {
try {
lock.wait(random.nextInt(10));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
//任务B 唤醒所有锁
class TaskB extends Task{
public TaskB(int index) {
super(index);
}
@Override
public void doSth() {
try {
synchronized (lock) {
lock.notifyAll();
Thread.sleep(random.nextInt(10));
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
//启动函数
public void start(){
ExecutorService service = Executors.newCachedThreadPool();
for(int i=0;i<LOCK_COUNT;++i){
service.execute(new TaskA(i));
service.execute(new TaskB(i));
}
}
//主函数入口
public static void main(String[] args) {
new SY_Hign_TestCase().start();
}
}
代码很简单,就是创建了2000个线程,让一定的线程去等待,另外一个线程去释放这些资源,这样就会有大量的线程切换,我们来看下效果。
很明显,
CPU 的内核占用率很高,我们拿具体的资源监视器看一下:
很明显可以看出有很多线程切换占用了大量的
CPU 资源。
同样的程序部署在Linux下,top视图如下图所示:
展开对应的
CPU 资源,我们可以清晰的看到如下情形:
大家可以看到有大量的
sy 内核占用,但是也有不少的 us , us 是因为我们启用了大量的循环,而 sy 是因为大量线程切换导致的。
我们也可以使用vmstat来查看,如下图所示:
二、文件
IO 消耗过大,磁盘队列高。
在windows环境下,我们可以使用资源监视器查看对应的IO消耗,如下图所示:
这里不但可以看到当前磁盘的负载信息,队列详情,还能看到每个单独的进程的资源消耗情况。
Linux下主要使用pidstat、iostat等进行分析。如下图所示
Pidstat –d –t –p [pid] {time} {count}
如:pidstat -d -t -p 18720 1 1
Iostat
Iostat –x xvda 1 10做定时采样
废话不多说,直接来示例,上干货!
package com.yhj.jvm.monitor.io;
import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* @Described:IO测试用例
* @author YHJ create at 2012-3-29 上午09:56:06
* @FileNmae com.yhj.jvm.monitor.io.IO_TestCase.java
*/
public class IO_TestCase {
private String fileNmae = "monitor.log";
private String context ;
// 和CPU处理器个数相同,既充分利用CPU资源,又导致线程频繁切换
private final static int THRED_COUNT = Runtime.getRuntime().availableProcessors();
public IO_TestCase() {//加长写文件的内容,拉长每次写入的时间
StringBuilder sb = new StringBuilder();
for(int i=0;i<1000;++i){
sb.append("context index :")
.append(i)
.append("\n");
this.context= new String(sb);
}
}
//写文件任务
class Task implements Runnable{
@Override
public void run() {
while(true){
BufferedWriter writer = null;
try {
writer = new BufferedWriter(new FileWriter(fileNmae,true));//追加模式
writer.write(context);
} catch (Exception e) {
e.printStackTrace();
}finally{
try {
writer.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
//启动函数
public void start(){
ExecutorService service = Executors.newCachedThreadPool();
for(int i=0;i<THRED_COUNT;++i)
service.execute(new Task());
}
//主函数入口
public static void main(String[] args) {
new IO_TestCase().start();
}
}
这段示例很简单,通过创建一个和CPU个数相同的线程池,然后开启这么多线程一起读写同一个文件,这样就会因IO资源的竞争而导致IO的队列很高,如下图所示:
关掉之后马上就下来了
我们把这个部署到
Linux 上观看。
这里的
%idle 指的是系统没有完成写入的数量占用 IO 总量的百分百,为什么这么高我们的系统还能承受?因为我这台机器的内存为16GB 的,我们来查看以下 top 视图就可以清晰的看到。
占用了大量的内存资源。
三、内存消耗
对于JVM的内存模型大家已经很清楚了,前面我们讲了JVM的性能监控工具。对于Java应用来说,出现问题主要消耗在于JVM的内存上,而JVM的内存,JDK已经给我们提供了很多的工具。在实际的生成环境,大部分应用会将-Xms和-Xmx设置为相同的,避免运行期间不断开辟内存。
对于内存消耗,还有一部分是直接物理内存的,不在堆空间,前面我们也写过对应的示例。之前一个系统就是因为有大量的NIO操作,而NIO是使用物理内存的,并且开辟的物理内存是在触发FULL GC的时候才进行回收的,但是当时的机器总内存为16GB 给堆的内存是14GB Edon为1.5GB,也就是实际剩下给物理呢哦村的只有0.5GB,最终导致总是发生内存溢出,但监控堆、栈的内存消耗都不大。在这里我就不多写了!
四、网络消耗过大
Windows下使用本地网络视图可以监控当前的网络流量大小
更详细的资料可以打开资源监视器,如下图所示
Linux
平台可以使用以下 sar 命令查看
sar -n DEV 1 2
字段说明:
rxpck/s:每秒钟接收的数据包
txpck/s:每秒钟发送的数据包
rxbyt/s:每秒钟接收的字节数
txbyt/s:每秒钟发送的字节数
rxcmp/s:每秒钟接收的压缩数据包
txcmp/s:每秒钟发送的压缩数据包
rxmcst/s:每秒钟接收的多播数据包
Java程序一般不会出现网络IO导致问题,因此在这里也不过的的阐述。
五、程序执行缓慢
当CPU、内存、磁盘、网络都不高,程序还是执行缓慢的话,可能引发的原因大致有以下几种:
1程序锁竞争过于激烈,比如你只有2颗CPU,但是你启用了200个线程,就会导致大量的线程等待和切换,而这不会导致CPU很高,但是很多线程等待意味着你的程序运行很慢。
2未充分利用硬件资源。比如你的机器是16个核心的,但是你的程序是单线程运行的,即使你的程序优化的很好,当需要处理的资源比较多的时候,程序还会很慢,因此现在都在提倡分布式,通过大量廉价的PC机来提升程序的执行速度!
3其他服务器反应缓慢,如数据库、缓存等。当大量做了分布式,程序CPU负载都很低,但是提交给数据库的sql无法很快执行,也会特别慢。
总结一下,当出现性能问题的时候我们该怎么做?
一、CPU过高
1、 us过高
使用监控工具快读定位哪里有死循环,大计算,对于死循环通过阻塞式队列解决,对于大计算,建议分配单独的机器做后台计算,尽量不要影响用户交互,如果一定要的话(如框计算、云计算),只能通过大量分布式来实现
2、 sy过高
最有效的方法就是减少进程,不是进程越多效率越高,一般来说线程数和CPU的核心数相同,这样既不会造成线程切换,又不会浪费CPU资源
二、内存消耗过高
1、 及时释放不必要的对象
2、 使用对象缓存池缓冲
3、 采用合理的缓存失效算法(还记得我们之前提到的弱引用、幽灵引用么?)
三、磁盘IO过高
1、 异步读写文件
2、 批量读写文件
3、 使用缓存技术
4、 采用合理的文件读写规则
四、网络
1、增加宽带流量
五、资源消耗不多但程序运行缓慢
1、使用并发包,减少锁竞争
2、对于必须单线程执行的使用队列处理
3、大量分布式处理
六、未充分利用硬件资源
1、 修改程序代码,使用多线程处理
2、 修正外部资源瓶颈,做业务拆分
3、 使用缓存
转自:http://yhjhappy234.blog.163.com/blog/static/3163283220122298232721/?suggestedreading&wumii
【转载】深入理解JVM性能调优的更多相关文章
- JVM性能调优监控工具jps、jstack、jmap、jhat、jstat、hprof使用详解
摘要: JDK本身提供了很多方便的JVM性能调优监控工具,除了集成式的VisualVM和jConsole外,还有jps.jstack.jmap.jhat.jstat.hprof等小巧的工具,本博客希望 ...
- JVM性能调优监控工具jps、jstack、jmap、jhat、jstat使用详解(转VIII)
JVM本身就是一个java进程,一个java程序运行在一个jvm进程中.多个java程序同时运行就会有多个jvm进程.一个jvm进程有多个线程至少有一个gc线程和一个用户线程. JDK本身提供了很多方 ...
- jvm 性能调优
[转载]:http://blog.csdn.net/chen77716/article/details/5695893 最近因项目存在内存泄漏,故进行大规模的JVM性能调优 , 现把经验做一记录. 一 ...
- JVM性能调优监控命令jps、jinfo、jstat、jmap+jhat、jstack使用详解
JDK本身提供了很多方便的JVM性能调优监控工具,除了集成式的VisualVM和jConsole外,还有jps.jinfo.jstat.jmap+jhat.jstack等小巧的工具,本博客希望能起抛砖 ...
- 《转》:JVM性能调优监控工具jps、jstack、jmap、jhat、jstat、hprof使用详解
原链接:https://my.oschina.net/feichexia/blog/196575 现实企业级Java开发中,有时候我们会碰到下面这些问题: OutOfMemoryError,内存不足 ...
- 【转】JVM性能调优监控工具jps、jstack、jmap、jhat、jstat使用详解
http://www.cnblogs.com/therunningfish/p/5524238.html JDK本身提供了很多方便的JVM性能调优监控工具,除了集成式的VisualVM和jConsol ...
- JDK自带JVM性能调优监控工具jps、jstack、jmap、jhat、jstat
原文地址:https://www.jianshu.com/p/db954cb968fb JVM性能调优监控工具jps.jstack.jmap.jhat.jstat位于JDK的bin目录,这些工具短小精 ...
- 如何合理的规划一次jvm性能调优
https://blog.csdn.net/miracle_8/article/details/78347172 摘要: JVM性能调优涉及到方方面面的取舍,往往是牵一发而动全身,需要全盘考虑各方面的 ...
- JVM性能调优监控工具jps、jstack、jmap、jhat、jstat, hprof使用详解
JDK本身提供了很多方便的JVM性能调优监控工具,除了集成式的VisualVM和jConsole外,还有jps.jstack.jmap.jhat.jstat等小巧的工具,本博客希望能起抛砖引玉之用,让 ...
随机推荐
- MFC 按钮
((CButton *)GetDlgItem(IDC_RADIO1))->SetCheck(TRUE);//选上 ((CButton *)GetDlgItem(IDC_RADIO1))-> ...
- EF4学习链接
原文发布时间为:2011-09-23 -- 来源于本人的百度文章 [由搬家工具导入] 1.Fluent API 的方式定义与数据库映射 2.利用特性实现与数据库的映射 3.EF的一些公约的介绍 4.E ...
- 多目录,多可执行文件的Makfile的编写
1.前言 在目前的工作中,我遇到这样的一个工作情景,可以认为我要开发一个库,这个库的有多个模块,每个模块的.c放到src中,.h放到include中,这应该是个标准做法. drwxr-xr-x. ro ...
- 一篇不错的v4l2入门文档【转】
转自:http://blog.chinaunix.net/uid-26851094-id-3270803.html 原帖地址: http://www.isongzi.com/2009/02/23/v4 ...
- WritePrivateProfileString等读写.ini配置文件
配置文件中经常用到ini文件,在VC中其函数分别为: 写入.ini文件: BOOL WritePrivateProfileString( LPCTSTR lpAppName, // INI文件中的一个 ...
- poj 1265&&poj 2954(Pick定理)
Area Time Limit: 1000MS Memory Limit: 10000K Total Submissions: 5811 Accepted: 2589 Description ...
- 【原创】BI解决方案选型之ETL数据整合工具对比
一.背景 在企业BI平台建设过程中,数据整合始终是一切的基础,简单BI项目可以通过存储过程来实现,而复杂.全面.多方异构数据来源等就大大增加了复杂性,存储过程的可管理性.可维护性.容错性等就无法很好的 ...
- (2)apache安装、配置及使用
一.apache安装 假设我们我们的安装路径是 C:\Apache2.2 运行软件 如果是本地学习使用 第二行服务名localhost 其他随便 一个自动安装,一个自定义安装,没有什么特别重要的,一直 ...
- UVA 1395 Slim Span 最小生成树
题意: 给你一个图,让你求这个图中所有生成树中满足题目条件的,这个条件是生成树中最长边与最短边的差值最小. 思路: 根据最小瓶颈生成树的定义:在一个有权值的无向图中,求一个生成树最大边的权值尽量小.首 ...
- nginx.conf及server配置
#服务运行用户 user sysadmin www; #工作进程数 worker_processes 4; #错误日志位置 error_log /data/sysadmin/service_logs/ ...