1:问题的抛出
今天在安卓项目中使用后台线程操作一个大文件,分块读取文件中的所有内容,每次操作加载一个小块进行解析,解析到指定的文本内容之后会加载并常驻内存中,即使所有我解析到的文本内容被加入到内存中也不会很大,这样不会造成内存泄露问题。原理如此,但是最终依然出现oom。
2:问题的排查
仔细检查之后发现线程中所有的产生的对象都已经在操作结束之后回收(即:生成的对象没有引用)。代码很简短,挨个排查,发现一个string对象调用了substring(int start, int end)方法,查看了一下substring方法的注释:
Returns a string containing a subsequence of characters from this string. The returned string shares this string's backing array.
注释中的这句话"shares this string's backing array"让我产生一个疑问:什么是backing array,会不会是原来string的所有内容数组?带着这个问题,我把调用该方法的代码注释掉再次运行,哈哈,果然顺畅了。
3:产生的原因
定位到String.substring(int start, int end)的源代码:
public String substring(int start, int end) {
if (start == 0 && end == count) {
return this;
}
// NOTE last character not copied!
// Fast range check.
if (start >= 0 && start <= end && end <= count) {
return new String(offset + start, end - start, value);
}
throw startEndAndLength(start, end);
}
private final char[] value;
public String(byte[] data, int offset, int byteCount, Charset charset) {
if ((offset | byteCount) < 0 || byteCount > data.length - offset) {
throw failedBoundsCheck(data.length, offset, byteCount);
}
// We inline UTF-8, ISO-8859-1, and US-ASCII decoders for speed and because 'count' and
// 'value' are final.
String canonicalCharsetName = charset.name();
if (canonicalCharsetName.equals("UTF-8")) {
byte[] d = data;
char[] v = new char[byteCount];
int idx = offset;
int last = offset + byteCount;
int s = 0;
outer:
while (idx < last) {
byte b0 = d[idx++];
if ((b0 & 0x80) == 0) {
// 0xxxxxxx
// Range: U-00000000 - U-0000007F
int val = b0 & 0xff;
v[s++] = (char) val;
} else if (((b0 & 0xe0) == 0xc0) || ((b0 & 0xf0) == 0xe0) ||
((b0 & 0xf8) == 0xf0) || ((b0 & 0xfc) == 0xf8) || ((b0 & 0xfe) == 0xfc)) {
int utfCount = 1;
if ((b0 & 0xf0) == 0xe0) utfCount = 2;
else if ((b0 & 0xf8) == 0xf0) utfCount = 3;
else if ((b0 & 0xfc) == 0xf8) utfCount = 4;
else if ((b0 & 0xfe) == 0xfc) utfCount = 5;
// 110xxxxx (10xxxxxx)+
// Range: U-00000080 - U-000007FF (count == 1)
// Range: U-00000800 - U-0000FFFF (count == 2)
// Range: U-00010000 - U-001FFFFF (count == 3)
// Range: U-00200000 - U-03FFFFFF (count == 4)
// Range: U-04000000 - U-7FFFFFFF (count == 5)
if (idx + utfCount > last) {
v[s++] = REPLACEMENT_CHAR;
continue;
}
// Extract usable bits from b0
int val = b0 & (0x1f >> (utfCount - 1));
for (int i = 0; i < utfCount; ++i) {
byte b = d[idx++];
if ((b & 0xc0) != 0x80) {
v[s++] = REPLACEMENT_CHAR;
idx--; // Put the input char back
continue outer;
}
// Push new bits in from the right side
val <<= 6;
val |= b & 0x3f;
}
// Note: Java allows overlong char
// specifications To disallow, check that val
// is greater than or equal to the minimum
// value for each count:
//
// count min value
// ----- ----------
// 1 0x80
// 2 0x800
// 3 0x10000
// 4 0x200000
// 5 0x4000000
// Allow surrogate values (0xD800 - 0xDFFF) to
// be specified using 3-byte UTF values only
if ((utfCount != 2) && (val >= 0xD800) && (val <= 0xDFFF)) {
v[s++] = REPLACEMENT_CHAR;
continue;
}
// Reject chars greater than the Unicode maximum of U+10FFFF.
if (val > 0x10FFFF) {
v[s++] = REPLACEMENT_CHAR;
continue;
}
// Encode chars from U+10000 up as surrogate pairs
if (val < 0x10000) {
v[s++] = (char) val;
} else {
int x = val & 0xffff;
int u = (val >> 16) & 0x1f;
int w = (u - 1) & 0xffff;
int hi = 0xd800 | (w << 6) | (x >> 10);
int lo = 0xdc00 | (x & 0x3ff);
v[s++] = (char) hi;
v[s++] = (char) lo;
}
} else {
// Illegal values 0x8*, 0x9*, 0xa*, 0xb*, 0xfd-0xff
v[s++] = REPLACEMENT_CHAR;
}
}
if (s == byteCount) {
// We guessed right, so we can use our temporary array as-is.
this.offset = 0;
this.value = v;
this.count = s;
} else {
// Our temporary array was too big, so reallocate and copy.
this.offset = 0;
this.value = new char[s];
this.count = s;
System.arraycopy(v, 0, value, 0, s);
}
} else if (canonicalCharsetName.equals("ISO-8859-1")) {
this.offset = 0;
this.value = new char[byteCount];
this.count = byteCount;
Charsets.isoLatin1BytesToChars(data, offset, byteCount, value);
} else if (canonicalCharsetName.equals("US-ASCII")) {
this.offset = 0;
this.value = new char[byteCount];
this.count = byteCount;
Charsets.asciiBytesToChars(data, offset, byteCount, value);
} else {
CharBuffer cb = charset.decode(ByteBuffer.wrap(data, offset, byteCount));
this.offset = 0;
this.count = cb.length();
if (count > 0) {
// We could use cb.array() directly, but that would mean we'd have to trust
// the CharsetDecoder doesn't hang on to the CharBuffer and mutate it later,
// which would break String's immutability guarantee. It would also tend to
// mean that we'd be wasting memory because CharsetDecoder doesn't trim the
// array. So we copy.
this.value = new char[count];
System.arraycopy(cb.array(), 0, value, 0, count);
} else {
this.value = EmptyArray.CHAR;
}
}
}
看看源码就终于明白了,value长度就是原有byte数组根据不同编码计算得到的结果,其内容自然是字符串中所有数据内容。
4:总结
java中的String.substring(int start, int end)方法返回的新字符串仍然保持原来字符串的数据引用,如果数据量比较大,这里需要注意一下会不会产生内存问题。