前几天发布应用出现了点问题,排查过程中同事发在群里一篇文章,仔细看了一下,还是挺有意思的,于是自己模拟了一下场景,
原文在这里,一次简单致命的错误:http://www.blogjava.net/xylz/archive/2012/03/15/371966.html
在频繁的进行字符串的操作增加的时候,我们会优先考虑使用StringBuilder和StringBuffer,其中StringBuffer是线程安全的。有个小地方需要慎重,就是toString()方法。
先看这段代码://读取文件,然后在文件的末尾追加一些特殊字符然后换行
public static void main(String[] args) throws Exception { BufferedReader reader = new BufferedReader( new FileReader("E:\\java_tools\\hatrix1.28\\hatrix.log")); StringBuilder sb = new StringBuilder(); String str = null; while((str=reader.readLine())!=null){ if(StringUtils.isNotEmpty(sb.toString())){ sb.append("$$$$$$$$\r"); } sb.append(str); } System.out.println(sb.toString()); }
上面标红的这里,在小应用或者数据量不大的情况下完全没有问题,但是在数据量大,并且并发多的时候,就会出现问题。
在Stringbuilder的代码中看toString()的代码如下
public String toString() { // Create a copy, don't share the array return new String(value, 0, count); }
每次调用toString方法,会重新new一个String出来。
在java.lang.String的代码中,看最后一行,value并不是直接指向的,而是通过系统拷贝函数进行的内存操作
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); }
java.util.Arrays中关于数组拷贝
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; }
那为啥会通过内存拷贝的形式来进行toString呢,原因在于StringBuilder内部有两个核心的属性,这两个记录了String中的内容,是字符数组的形式。
这时候对于字符数组操作,通过内存拷贝是最快的方式了。
/** * The value is used for character storage. */ char value[]; /** * The count is the number of characters used. */ int count;
上述代码中,CPU会一直忙于进行内存的分配工作,会导致机器的load过高
解决办法:
用StringBuilder类中的length来判断是否有数值,这样就避免了无谓的内存操作。