线上服务器负载过高发生了报警,同事找我求救。
我看到机器的负载都超过20了,查看java进程线程栈,找到了出问题的代码。
下面是其代码片段,实际情况错误处理比这更坏。
2
3 import java.io.BufferedReader;
4 import java.io.InputStream;
5 import java.io.InputStreamReader;
6 import java.net.HttpURLConnection;
7 import java.net.URL;
8 import java.net.URLConnection;
9 import org.apache.commons.lang.StringUtils;
10
11 /**
12 * @author adyliu (imxylz#gmail.com)
13 * @since 2012-3-15
14 */
15 public class FaultDemo {
16
17 /**
18 * @param args
19 */
20 public static void main(String[] args) throws Exception {
21 final String tudou = "http://v.youku.com/v_playlist/f17170661o1p9.html";
22
23 URL url = new URL(tudou);
24 HttpURLConnection conn = (HttpURLConnection) url.openConnection();
25 conn.connect();
26 try {
27 InputStream in = conn.getInputStream();
28 BufferedReader br = new BufferedReader(new InputStreamReader(in, "utf-8"));
29 StringBuilder buf = new StringBuilder();
30 String line = null;
31 while ((line = br.readLine()) != null) {
32 if (StringUtils.isNotEmpty(buf.toString())) {
33 buf.append("\r\n");
34 }
35 buf.append(line);
36 }
37 //do something with 'buf'
38
39 } finally {
40 conn.disconnect();
41 }
42
43 }
44
45 }
46
思考下,这段代码有什么致命问题么?(这里不追究业务逻辑处理的正确性以及细小的瑕疵)
.
..
...
现在回来。
我发现线程栈里面的线程都RUNNABLE在32行。
这一行看起来有什么问题呢?StringBuilder.toString()不是转换成String么?Apache commons-lang里面的StringUtils.isNotEmpty使用也没问题啊?
看代码,人家的逻辑其实是判断是否是第一行,如果不是第一行那么就增加一个换行符。
既然CPU在这里运行,那么就说明这个地方一定存在非常耗费CPU的操作,导致CPU非常繁忙,从而系统负载过高。
看详细堆栈,其实CPU在进行内存的拷贝动作。
看下面的源码。
java.lang.StringBuilder.toString()
// Create a copy, don't share the array
return new String(value, 0, count);
}
接着看java.lang.String的构造函数:
if (offset < 0) {
throw new StringIndexOutOfBoundsException(offset);
}
if (count < 0) {
throw new StringIndexOutOfBoundsException(count);
}
// Note: offset or count might be near -1>>>1.
if (offset > value.length - count) {
throw new StringIndexOutOfBoundsException(offset + count);
}
this.offset = 0;
this.count = count;
this.value = Arrays.copyOfRange(value, offset, offset+count);
}
看出来了么?
问题的关键在于String构造函数的最后一行,value并不是直接指向的,而是重新生成了一个新的字符串,使用系统拷贝函数进行内存复制。
int newLength = to - from;
if (newLength < 0)
throw new IllegalArgumentException(from + " > " + to);
char[] copy = new char[newLength];
System.arraycopy(original, from, copy, 0,
Math.min(original.length - from, newLength));
return copy;
}
好了,再回头看逻辑代码32行。
buf.append("\r\n");
}
这里有问题的地方在于每次循环一行的时候都生成一个新的字符串。也就是说如果HTTP返回的结果输入流中有1000行的话,将额外生成1000个字符串(不算StringBuilder扩容生成的个数)。每一个字符串还比前一个字符串大。
我们来做一个简单的测试,我们在原来的代码上增加几行计数代码。
int count = 0;
int malloc = 0;
while ((line = br.readLine()) != null) {
lines++;
count+=line.length();
malloc += count;
if (StringUtils.isNotEmpty(buf.toString())) {
buf.append("\r\n");
}
buf.append(line);
}
System.out.println(lines+" -> "+count+" -> "+malloc);
我们记录下行数lines以及额外发生的字符串拷贝大小malloc。
这是一次输出的结果。
也就是1169行的网页,一共是66958字节(65KB),结果额外生成的内存大小(不算StringBuilder扩容占用的内存大小)为39356387字节(37.5MB)!!!
试想一下,CPU一直频繁于进行内存分配,机器的负载能不高么?我们线上服务器是2个CPU 16核,内存24G的Redhat Enterprise Linux 5.5,负载居然达到几十。这还是只有访问量很低的时候。这就难怪服务频繁宕机了。
事实上我们有非常完善和丰富的基于Apache commons-httpclient的封装,操作起来也非常简单。对于这种简单的请求,只需要一条命令就解决了。
String platform.utils.HttpClientUtils.postResponse(String, Map<String, String>)
即使非要自造轮子,处理这种简单的输入流可以使用下面的代码,就可以很好的解决问题。
ByteArrayOutputStream baos = new ByteArrayOutputStream(8192);
int len = -1;
byte[] b = new byte[8192];//8k
while ((len = in.read(b)) > 0) {
baos.write(b, 0, len);
}
baos.close();//ignore is ok
String response = new String(baos.toByteArray(), encoding);
当然了,最后紧急处理线上问题最快的方式就是将有问题的代码稍微变通下即可。
buf.append("\r\n");
}
这个问题非常简单,只是想表达几个观点:
- 团队更需要合作,按照规范来进行。自造轮子不是不可以,但是生产环境还是要限于自己熟悉的方式。
- 即使非常简单的代码,也有可能有致命的陷阱在里面。善于思考才是王道。
- 学习开源的代码和常规思路,学习解决问题的常规做法。这个问题其实非常简单,熟悉输入输出流的人非常熟练就能解决问题。
转载地址:http://www.blogjava.net/xylz/archive/2012/03/15/371966.html
相关推荐
磁盘打不开设备硬件出现致命错误,导致请求失败,是因为这个I盘的文件系统内部结构损坏导致的。要恢复里面的数据就必须要注意,这个盘不能格式化,否则数据会进一步损坏。具体的恢复方法看正文
注意:3.8.1版本重构了PHP扩展,不更新扩展会导致网站出现 此站点遇到了致命错误 ,其他平台购买的就不要到本站来问怎么安装,主题本身就可能有问题,没破解完整你怎么安装都一个样 本资源为PHP修复扩展。 404错误...
解决VS 中 出现致命错误 RC1015: 无法打开包含文件 'afxres.h' 问题
SQLServer的SQL对话让你能够轻松地处理可能会在存储进程、函数里发生的非致命错误,但是并不是所有的错误都很容易处理。事实上,致命和非致命的错误有很多。什么是致命的,什么是非致命的,对此没有很完备的文档说明...
解决VC++6.0当中出现的致命错误,这个方法很有效,有用
JVM致命错误完全解析:基于现实案例
致命错误 RC1015: 无法打开包含文件 'afxres.h'. 执行 rc.exe 时出错解决办法 网上大部分人都说是安装路径缺错,或者只贴出这一个头文件其实并非如此 我特地把mfc下整个目录全部覆盖测试,亲测完美解决。 找到你对应...
初中语文文摘人生隐鱼的致命错误
投机市中的七种致命错误.pptx
投机市中的七种致命错误.ppt
AUTOCAD_致命错误处理办法(论坛).docx
7 个致命的 Linux 命令 这几个命令使用后会对linux系统造成不可挽回的损失
cad保存文件时出现文件错误,加载并运行一些好的lisp程序,几个解决
致命错误 RC1015: 无法打开包含文件 'afxres.h'. 执行 rc.exe 时出错解决办法 网上大部分人都说是安装路径缺错,或者只贴出这一个头文件其实并非如此 我特地把mfc下整个目录全部覆盖测试,亲测完美解决。 找到你对应...
男人与女人相处最容易出现的五大致命错误.pdf
fate的error信息,用来描述手机在某时间段的操作
(一)、致命错误英汉对照及处理方法: A-B致命错误 Bad call of in-line function (内部函数非法调用) 分析与处理:在使用一个宏定义的内部函数时,没能正确调用。一个内部函数以两个下划线(__)开始和结束。
https://blog.csdn.net/qq_35000950/article/details/105868274 单面打印机双面打印———python pdf拆分重排
进销存软件常见误区,中小企业进销存应用的三个致命错误.docx
无限试驾致命轨道吸引力致命轨道吸引力致命轨道吸引力致命轨道吸引力致命轨道吸引力致命轨道吸引力致命轨道吸引力