`
cfyme
  • 浏览: 275088 次
  • 性别: Icon_minigender_1
  • 来自: 杭州
社区版块
存档分类
最新评论

(转载)一次简单却致命的错误

 
阅读更多

线上服务器负载过高发生了报警,同事找我求救。
我看到机器的负载都超过20了,查看java进程线程栈,找到了出问题的代码。

下面是其代码片段,实际情况错误处理比这更坏。

 1 package demo;
 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()

    public String toString() {
        // Create a copy, don't share the array
    return new String(value, 0, count);
    }

接着看java.lang.String的构造函数:

    public String(char value[], int offset, int count) {
        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并不是直接指向的,而是重新生成了一个新的字符串,使用系统拷贝函数进行内存复制。

java.util.Arrays.copyOfRange(char[], int, int)
    public static char[] copyOfRange(char[] original, int from, int to) {
        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行。

if (StringUtils.isNotEmpty(buf.toString())) {
    buf.append("\r\n");
}

这里有问题的地方在于每次循环一行的时候都生成一个新的字符串。也就是说如果HTTP返回的结果输入流中有1000行的话,将额外生成1000个字符串(不算StringBuilder扩容生成的个数)。每一个字符串还比前一个字符串大。


我们来做一个简单的测试,我们在原来的代码上增加几行计数代码。

    int lines =0;
    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 -> 39356387

也就是1169行的网页,一共是66958字节(65KB),结果额外生成的内存大小(不算StringBuilder扩容占用的内存大小)为39356387字节(37.5MB)!!!
试想一下,CPU一直频繁于进行内存分配,机器的负载能不高么?我们线上服务器是2个CPU 16核,内存24G的Redhat Enterprise Linux 5.5,负载居然达到几十。这还是只有访问量很低的时候。这就难怪服务频繁宕机了。

事实上我们有非常完善和丰富的基于Apache commons-httpclient的封装,操作起来也非常简单。对于这种简单的请求,只需要一条命令就解决了。

String platform.utils.HttpClientUtils.getResponse(String)
String platform.utils.HttpClientUtils.postResponse(String, Map<String, String>)


即使非要自造轮子,处理这种简单的输入流可以使用下面的代码,就可以很好的解决问题。

    InputStream in = 
    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);


当然了,最后紧急处理线上问题最快的方式就是将有问题的代码稍微变通下即可。

    if (buf.length() > 0) {
        buf.append("\r\n");
    }



这个问题非常简单,只是想表达几个观点:

  • 团队更需要合作,按照规范来进行。自造轮子不是不可以,但是生产环境还是要限于自己熟悉的方式。
  • 即使非常简单的代码,也有可能有致命的陷阱在里面。善于思考才是王道。
  • 学习开源的代码和常规思路,学习解决问题的常规做法。这个问题其实非常简单,熟悉输入输出流的人非常熟练就能解决问题。

转载地址:http://www.blogjava.net/xylz/archive/2012/03/15/371966.html

分享到:
评论

相关推荐

    CEOMAX总裁主题安装时遇到此站点遇到了致命错误 php扩展安装修复补丁

    注意:3.8.1版本重构了PHP扩展,不更新扩展会导致网站出现 此站点遇到了致命错误 ,其他平台购买的就不要到本站来问怎么安装,主题本身就可能有问题,没破解完整你怎么安装都一个样 本资源为PHP修复扩展。 404错误...

    如何恢复设备硬件出现致命错误,导致请求失败F盘的资料

    磁盘打不开设备硬件出现致命错误,导致请求失败,是因为这个I盘的文件系统内部结构损坏导致的。要恢复里面的数据就必须要注意,这个盘不能格式化,否则数据会进一步损坏。具体的恢复方法看正文

    致命错误 RC1015: 无法打开包含文件 'afxres.h'. 执行 rc.exe 时出错解决办法

    致命错误 RC1015: 无法打开包含文件 'afxres.h'. 执行 rc.exe 时出错解决办法 网上大部分人都说是安装路径缺错,或者只贴出这一个头文件其实并非如此 我特地把mfc下整个目录全部覆盖测试,亲测完美解决。 找到你对应...

    JVM致命错误完全解析:基于现实案例

    JVM 致命错误日志详解 JVM 致命错误日志是 Java 虚拟机(JVM)在遇到致命错误时生成的日志文件,用于记录错误信息和系统信息。该日志文件可以帮助开发者和维护者快速定位和解决问题。 文件头:文件头是错误日志的...

    PHP各种异常和错误的拦截方法及发生致命错误时进行报警

    然而,这种方式会造成一个矛盾:虽然在线上环境中隐藏了错误信息,但如果错误和异常未被记录到日志中,将很难追踪和诊断问题。为了解决这个问题,可以使用PHP提供的错误和异常处理机制,即使用内建的函数来实现错误...

    VS2017中出现致命错误 RC1015: 无法打开包含文件 &amp;amp;#039;afxres.h&amp;amp;#039; 问题

    在使用Visual Studio 2017 (VS2017) 进行开发时,有时可能会遇到一个编译错误,提示“致命错误 RC1015: 无法打开包含文件 'afxres.h'”。这个错误通常发生在尝试编译资源文件(如.rc文件)时,而系统找不到必需的...

    投机市中的七种致命错误.pptx

    致命错误一:未能迅速止损 止损是风险管理的重要组成部分,是决定交易者能否在市场中长期存活的关键。止损点的设置并非一成不变,需根据交易者的策略和市场状况灵活调整。为了有效地止损,交易者需要学会接受损失,...

    fatl错误信息

    “fatl错误信息”虽然看起来可能只是一个简单的日志条目,但它背后隐藏着重要的调试线索和技术信息。通过对这些错误信息的仔细分析和解读,我们不仅可以了解问题发生的具体情况,还能据此采取适当的措施进行修复。这...

    VC++6.0当中出现的致命错误

    ### VC++6.0 当中出现的致命错误:RC1015无法找到头文件 'afxres.h' 在使用 Visual C++ 6.0(以下简称 VC++ 6.0)进行开发时,开发者可能会遇到一个名为“RC1015”的错误提示:“无法找到头文件 'afxres.h'”。这个...

    S7-200SMART PLC下载程序时报错,错误代码0082-解决办法.docx

    错误代码0082指示了一个"指令非法"的问题,这意味着在尝试执行的某个操作违反了编程语言的规定或者硬件的限制。在本例中,错误的具体原因是调用了一个带有超过最大参数数量的子程序。 S7-200SMART PLC的编程规范...

    投机市中的七种致命错误.ppt

    投机市中的七种致命错误.ppt

    上传错了,不要过审核,代码有致命性错误

    标题中的“上传错了,不要过审核,代码有致命性错误”可能是指作者在发布时遇到了问题,可能存在一个或多个导致程序无法正常运行的关键错误。不过,由于标题并未提供具体的技术细节,我们将主要根据描述和标签来探讨...

    初中语文文摘人生隐鱼的致命错误

    《初中语文文摘》中提及的故事“人生隐鱼的致命错误”,揭示了一个深刻的道理,不仅适用于生物学中的自然法则,更是对我们人类生活的一种警示。故事的主角——隐鱼,以其独特的生存方式,为我们提供了一个生动的寓言...

    AUTOCAD_致命错误处理办法(论坛).docx

    AUTOCAD_致命错误处理办法(论坛).docx

    致命错误(mysql mysql.h)没有那个文件或目录解决办法.docx

    标题 "致命错误(mysql mysql.h)没有那个文件或目录解决办法" 涉及的问题是,在尝试编译一个包含 MySQL C API 的程序时遇到了一个错误,提示找不到 `mysql/mysql.h` 头文件。这个错误通常意味着开发环境缺少了必要的 ...

    用SQL Server数据库处理数据层错误

    这个函数应该紧跟在可能导致错误的语句后面使用,因为它只保留最近一次执行的错误信息。例如,在存储过程中,当更新操作失败时,可以通过检查`@@Error`的值来决定是否需要回滚事务。 `RAISERROR` 语句则允许开发...

    7 个致命的 Linux 命令

    ### 7 个致命的 Linux 命令 在 Linux 操作系统中,存在一些非常危险的命令,这些命令一旦被误用或恶意利用,可能会对系统造成不可逆转的损害。下面详细介绍这七个命令及其潜在的风险。 #### 1. `rm -rf /` **命令...

    保存cad时出现的错误,清理后保存即可解决1111.lsp

    cad保存文件时出现文件错误,加载并运行一些好的lisp程序,几个解决

    致命错误 RC1015: 无法打开包含文件 'afxres.h'. 执行 rc.exe 时出错解决办法 网上大部分人都说是安装路

    致命错误 RC1015: 无法打开包含文件 'afxres.h'. 执行 rc.exe 时出错解决办法 网上大部分人都说是安装路径缺错,或者只贴出这一个头文件其实并非如此 我特地把mfc下整个目录全部覆盖测试,亲测完美解决。 找到你对应...

    delphi错误代码.pdf

    错误分为三类:I/O错误、致命错误和其他错误。 I/O错误(100-149)主要涉及到文件操作,例如: 1. 错误100:磁盘读错误,通常在尝试读取超出文件末尾时发生。 2. 错误101:磁盘写错误,可能是因为磁盘空间不足。 3....

Global site tag (gtag.js) - Google Analytics