jdk1.6后对字符串常量池做了改动,从源码到表现都做了很多改动。都是在规避了OOM的问题。下面说一些改动细节以及一些常见的错误认识。
subString的变动 1.6的实现如下 public String substring(int beginIndex, int endIndex) {
if (beginIndex < 0) {
throw new StringIndexOutOfBoundsException(beginIndex);
}
if (endIndex > count) {
throw new StringIndexOutOfBoundsException(endIndex);
}
if (beginIndex > endIndex) {
throw new StringIndexOutOfBoundsException(endIndex - beginIndex);
}
return ((beginIndex == 0) && (endIndex == count)) ? this :
new String(offset + beginIndex, endIndex - beginIndex, value);
}
这段代码逻辑没有问题,但是最后一句话改良了。问题出在这个String的构造方法上。
String(int offset, int count, char value[]) {
this.value = value;
this.offset = offset;
this.count = count;
}
这里直接把偏移量,长度和char数组都记录下来。当这执行subString的字符串被GC时,此时的char[]就内存泄露了。因为char数组多余的部分本来应该被回收的。
1.7直接使用了其他构造方法 public String substring(int beginIndex) {
if (beginIndex < 0) {
throw new StringIndexOutOfBoundsException(beginIndex);
}
int subLen = value.length - beginIndex;
if (subLen < 0) {
throw new StringIndexOutOfBoundsException(subLen);
}
return (beginIndex == 0) ? this : new String(value, beginIndex, subLen);
}
调用下面的构造方法
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.value = Arrays.copyOfRange(value, offset, offset+count);
}
直接调用下面这个构造方法,直接执行了拷贝操作,虽然多了一部操作,同时也消除了内存泄露的问题。
这个点是从葛一鸣老师的书中看到的。很符合这个专题,详细的还请看老师的书。
intern的变动具体的我原来在博客中写了,可以看
String的1.7变动:https://my.oschina.net/xpbob/blog/751405
String的1.6的表现以及场景问题:https://my.oschina.net/xpbob/blog/746488
主要变动就是字符串常量池的位置发生了改变,由方法区转化到了堆中。intern返回的是堆中的第一次出现且intern的字符串的String对象。
intern的操作最后参考SymbolTable.cpp中的intern方法,1.6和1.7都是这个。
String的gc这个细节是上intern的变动发现的。
最开始我的理解是字符串常量基本不会被回收,只会随着类的回收而回收。事实情况不是这样的。
public static void main(String[] args) {
show();
System.gc();
show();
}
public static void show(){
String s =new String(new char[]{'a','b'});
System.out.println(System.identityHashCode(s));
System.out.println(System.identityHashCode(s.intern()));
}
上面这段代码最好在jdk1.7以上执行,效果比较明显,你会发现,show方法中字符串ab和常量池中的ab的地址是一样的(这里说object是hashcode基本等同于地址,虽然事实并不是,只是和重写的hash来做区分),如果没有执行gc,那么结果都是常量池中的ab总会和第一次新建的ab的地址保持一致。
最开始我以为是gc的时候from到to区域,所以地址变了,仔细调试看了唯一Id发现不是。
intern的字符串被gc掉了。因为最初网上有用Intern来把字符串放入常量池来减少保证地址比较相同的做法。这个做法没什么问题。只是如果要保存地址值来做标识,那就会出问题了。
回收的代码,1.6和1.7也是有点不同的。
1.6的代码在SymbolTable.hpp中的unlink方法。
1.7的代码在SymbolTable.cpp中的unlink方法。
都是在gc执行的时候操作。只要没有堆栈对这个string持有引用,就可以被回收,这样就避免了太多的字符串加入到常量池中,把常量池所在的区域给oom了。
共同学习,写下你的评论
评论加载中...
作者其他优质文章