- 浏览: 235879 次
- 性别:
- 来自: 南京
最新评论
-
baby8117628:
vc下mp3 IDv1和IDV2的读取 -
gezexu:
你好,我按照你的步骤一步步进行但是安装libvorbis的时候 ...
linux如何搭建强大的FFMPEG环境 -
ini_always:
帅哥,转载也把格式做好点,另外出处也要注明一下吧。。。
MP3文件格式解析
一直赞叹Sun对待技术的严谨和优雅(bless Sun)。Sun JDK中Java库的源代码,连注释都清清楚楚、规规范范,javadoc注解的使用也一丝不苟,读起来很熟舒服。因此,在日常工作和学习中,经常读读 Java库的源代码,不亦乐乎?如果遇到诡异问题,源代码的帮助就更大了。
闲话少说,回归正题。这几天,一直在为Java的“内存泄露”问题纠结。Java应用程序占用的内存在不断的、有规律的上涨,最终超过了监控阈值。福尔摩斯不得不出手了!
说起Java的内存泄露,其实定义不是那么明确。首先,如果JVM没有bug,那么理论上是不会出现“无法回收的堆空间”,也就是说C/C++中的那种内存泄露在Java中不存在的。其次,即使Java程序一直持有某个对象的引用,但是从程序逻辑上看,这个对象再也不会被用到了,那么我们可以认为这个对象被泄露了。如果这样的对象数量很多,那么很明显,大量的内存空间就被泄露(“浪费”更准确一些)了。
不过,本文要说的内存泄露,并不属于上述原因,因此打上了引号。其具体原因,确实出乎意料。欲知详情,请看下面讲解。
分析内存泄露的一般步骤
如果发现Java应用程序占用的内存出现了泄露的迹象,那么我们一般采用下面的步骤分析
1. 把Java应用程序使用的heap dump下来
2. 使用Java heap分析工具,找出内存占用超出预期(一般是因为数量太多)的嫌疑对象
3. 必要时,需要分析嫌疑对象和其他对象的引用关系。
4. 查看程序的源代码,找出嫌疑对象数量过多的原因。
dump heap
如果Java应用程序出现了内存泄露,千万别着急着把应用杀掉,而是要保存现场。如果是互联网应用,可以把流量切到其他服务器。保存现场的目的就是为了把运行中JVM的heap dump下来。
JDK自带的jmap工具,可以做这件事情。它的执行方法是:
jmap -dump:format=b,file=heap.bin <pid>
format=b的含义是,dump出来的文件时二进制格式。
file-heap.bin的含义是,dump出来的文件名是heap.bin。
<pid>就是JVM的进程号。
(在linux下)先执行ps aux | grep java,找到JVM的pid;然后再执行jmap -dump:format=b,file=heap.bin <pid>,得到heap dump文件。
analyze heap
将二进制的heap dump文件解析成human-readable的信息,自然是需要专业工具的帮助,这里推荐Memory Analyzer 。
Memory Analyzer,简称MAT,是Eclipse基金会的开源项目,由SAP和IBM捐助。巨头公司出品的软件还是很中用的,MAT可以分析包含数亿级对象的heap、快速计算每个对象占用的内存大小、对象之间的引用关系、自动检测内存泄露的嫌疑对象,功能强大,而且界面友好易用。
MAT的界面基于Eclipse开发,以两种形式发布:Eclipse插件和Eclipe RCP。MAT的分析结果以图片和报表的形式提供,一目了然。总之个人还是非常喜欢这个工具的。下面先贴两张官方的screenshots:
MAT的分析结果概述
MAT分析对象的大小及数量
言归正传,我用MAT打开了heap.bin,很容易看出,char[]的数量出其意料的多,占用90%以上的内存 。一般来说,char[]在JVM确实会占用很多内存,数量也非常多,因为String对象以char[]作为内部存储。但是这次的char[]太贪婪了,仔细一观察,发现有数万计的char[],每个都占用数百K的内存 。这个现象说明,Java程序保存了数以万计的String 大 对象 。结合程序的逻辑,这个是不应该的,肯定在某个地方出了问题。
顺藤摸瓜
在可疑的char[]中,任意挑了一个,使用Path To GC Root功能,找到该char[]的引用路径,发现String对象是被一个HashMap中引用的 。这个也是意料中的事情,Java的内存泄露多半是因为对象被遗留在全局的HashMap中得不到释放。不过,该HashMap被用作一个缓存,设置了缓存条目的阈值,到达到阈值后会自动淘汰。从这个逻辑分析,应该不会出现内存泄露的。虽然缓存中的String对象已经达到数万计,但仍然没有达到预先设置的阈值(阈值设置地比较大,因为当时预估String对象都比较小)。
但是,另一个问题引起了我的注意:为什么缓存的String对象如此巨大?内部char[]的长度达数百K。虽然缓存中的String对象数量还没有达到阈值,但是String对象大小远远超出了我们的预期,最终导致内存被大量消耗,形成内存泄露的迹象(准确说应该是内存消耗过多) 。
就这个问题进一步顺藤摸瓜,看看String大对象是如何被放到HashMap中的。通过查看程序的源代码,我发现,确实有String大对象,不过并没有把String大对象放到HashMap中,而是把String大对象进行split(调用String.split方法),然后将split出来的String小对象放到HashMap中 了。
这就奇怪了,放到HashMap中明明是split之后的String小对象,怎么会占用那么大空间呢?难道是String类的split方法有问题?
查看代码
带着上述疑问,我查阅了Sun JDK6中String类的代码,主要是是split方法的实现:
view plaincopy to clipboardprint?
1. public String[] split(String regex, int limit) {
2. return Pattern.compile(regex).split(this, limit);
3. }
public String[] split(String regex, int limit) { return Pattern.compile(regex).split(this, limit); }
可以看出,Stirng.split方法调用了Pattern.split方法。继续看Pattern.split方法的代码:
view plaincopy to clipboardprint?
1. public String[] split(CharSequence input, int limit) {
2. int index = 0;
3. boolean matchLimited = limit > 0;
4. ArrayList<String> matchList = new ArrayList<String>();
5. Matcher m = matcher(input);
6. // Add segments before each match found
7. while(m.find()) {
8. if (!matchLimited || matchList.size() < limit - 1) {
9. String match = input.subSequence(index, m.start()).toString();
10. matchList.add(match);
11. index = m.end();
12. } else if (matchList.size() == limit - 1) { // last one
13. String match = input.subSequence(index,
14. input.length()).toString();
15. matchList.add(match);
16. index = m.end();
17. }
18. }
19. // If no match was found, return this
20. if (index == 0)
21. return new String[] {input.toString()};
22. // Add remaining segment
23. if (!matchLimited || matchList.size() < limit)
24. matchList.add(input.subSequence(index, input.length()).toString());
25. // Construct result
26. int resultSize = matchList.size();
27. if (limit == 0)
28. while (resultSize > 0 && matchList.get(resultSize-1).equals(""))
29. resultSize--;
30. String[] result = new String[resultSize];
31. return matchList.subList(0, resultSize).toArray(result);
32. }
public String[] split(CharSequence input, int limit) { int index = 0; boolean matchLimited = limit > 0; ArrayList<String> matchList = new ArrayList<String>(); Matcher m = matcher(input); // Add segments before each match found while(m.find()) { if (!matchLimited || matchList.size() < limit - 1) { String match = input.subSequence(index, m.start()).toString(); matchList.add(match); index = m.end(); } else if (matchList.size() == limit - 1) { // last one String match = input.subSequence(index, input.length()).toString(); matchList.add(match); index = m.end(); } } // If no match was found, return this if (index == 0) return new String[] {input.toString()}; // Add remaining segment if (!matchLimited || matchList.size() < limit) matchList.add(input.subSequence(index, input.length()).toString()); // Construct result int resultSize = matchList.size(); if (limit == 0) while (resultSize > 0 && matchList.get(resultSize-1).equals("")) resultSize--; String[] result = new String[resultSize]; return matchList.subList(0, resultSize).toArray(result); }
注意看第9行:Stirng match = input.subSequence(intdex, m.start()).toString();
这里的match就是split出来的String小对象,它其实是String大对象subSequence的结果。继续看String.subSequence的代码:
view plaincopy to clipboardprint?
1. public CharSequence subSequence(int beginIndex, int endIndex) {
2. return this.substring(beginIndex, endIndex);
3. }
public CharSequence subSequence(int beginIndex, int endIndex) { return this.substring(beginIndex, endIndex); }
String.subSequence有调用了String.subString,继续看:
view plaincopy to clipboardprint?
1. public String substring(int beginIndex, int endIndex) {
2. if (beginIndex < 0) {
3. throw new StringIndexOutOfBoundsException(beginIndex);
4. }
5. if (endIndex > count) {
6. throw new StringIndexOutOfBoundsException(endIndex);
7. }
8. if (beginIndex > endIndex) {
9. throw new StringIndexOutOfBoundsException(endIndex - beginIndex);
10. }
11. return ((beginIndex == 0) && (endIndex == count)) ? this :
12. new String(offset + beginIndex, endIndex - beginIndex, value);
13. }
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); }
看第11、12行,我们终于看出眉目,如果subString的内容就是完整的原字符串,那么返回原String对象;否则,就会创建一个新的 String对象,但是这个String对象貌似使用了原String对象的char[]。我们通过String的构造函数确认这一点:
view plaincopy to clipboardprint?
1. // Package private constructor which shares value array for speed.
2. String(int offset, int count, char value[]) {
3. this.value = value;
4. this.offset = offset;
5. this.count = count;
6. }
// Package private constructor which shares value array for speed. String(int offset, int count, char value[]) { this.value = value; this.offset = offset; this.count = count; }
为了避免内存拷贝、加快速度,Sun JDK直接复用了原String对象的char[],偏移量和长度来标识不同的字符串内容。也就是说,subString出的来String小对象仍然会指向原String大对象的char[],split也是同样的情况 。这就解释了,为什么HashMap中String对象的char[]都那么大。
原因解释
其实上一节已经分析出了原因,这一节再整理一下:
1. 程序从每个请求中得到一个String大对象,该对象内部char[]的长度达数百K。
2. 程序对String大对象做split,将split得到的String小对象放到HashMap中,用作缓存。
3. Sun JDK6对String.split方法做了优化,split出来的Stirng对象直接使用原String对象的char[]
4. HashMap中的每个String对象其实都指向了一个巨大的char[]
5. HashMap的上限是万级的,因此被缓存的Sting对象的总大小=万*百K=G级。
6. G级的内存被缓存占用了,大量的内存被浪费,造成内存泄露的迹象。
解决方案
原因找到了,解决方案也就有了。split是要用的,但是我们不要把split出来的String对象直接放到HashMap中,而是调用一下String的拷贝构造函数String(String original),这个构造函数是安全的,具体可以看代码:
view plaincopy to clipboardprint?
1. /**
2. * Initializes a newly created {@code String} object so that it represents
3. * the same sequence of characters as the argument; in other words, the
4. * newly created string is a copy of the argument string. Unless an
5. * explicit copy of {@code original} is needed, use of this constructor is
6. * unnecessary since Strings are immutable.
7. *
8. * @param original
9. * A {@code String}
10. */
11. public String(String original) {
12. int size = original.count;
13. char[] originalValue = original.value;
14. char[] v;
15. if (originalValue.length > size) {
16. // The array representing the String is bigger than the new
17. // String itself. Perhaps this constructor is being called
18. // in order to trim the baggage, so make a copy of the array.
19. int off = original.offset;
20. v = Arrays.copyOfRange(originalValue, off, off+size);
21. } else {
22. // The array representing the String is the same
23. // size as the String, so no point in making a copy.
24. v = originalValue;
25. }
26. this.offset = 0;
27. this.count = size;
28. this.value = v;
29. }
/** * Initializes a newly created {@code String} object so that it represents * the same sequence of characters as the argument; in other words, the * newly created string is a copy of the argument string. Unless an * explicit copy of {@code original} is needed, use of this constructor is * unnecessary since Strings are immutable. * * @param original * A {@code String} */ public String(String original) { int size = original.count; char[] originalValue = original.value; char[] v; if (originalValue.length > size) { // The array representing the String is bigger than the new // String itself. Perhaps this constructor is being called // in order to trim the baggage, so make a copy of the array. int off = original.offset; v = Arrays.copyOfRange(originalValue, off, off+size); } else { // The array representing the String is the same // size as the String, so no point in making a copy. v = originalValue; } this.offset = 0; this.count = size; this.value = v; }
只是,new String(string)的代码很怪异,囧。或许,subString和split应该提供一个选项,让程序员控制是否复用String对象的char[]。
是否是Bug
虽然,subString和split的实现造成了现在的问题,但是这能否算String类的bug呢?个人觉得不好说。因为这样的优化是比较合理的,subString和spit的结果肯定是原字符串的连续子序列。只能说,String不仅仅是一个核心类,它对于JVM来说是与原始类型同等重要的类型。
JDK实现对String做各种可能的优化都是可以理解的。但是优化带来了忧患,我们程序员足够了解他们,才能用好他们。
闲话少说,回归正题。这几天,一直在为Java的“内存泄露”问题纠结。Java应用程序占用的内存在不断的、有规律的上涨,最终超过了监控阈值。福尔摩斯不得不出手了!
说起Java的内存泄露,其实定义不是那么明确。首先,如果JVM没有bug,那么理论上是不会出现“无法回收的堆空间”,也就是说C/C++中的那种内存泄露在Java中不存在的。其次,即使Java程序一直持有某个对象的引用,但是从程序逻辑上看,这个对象再也不会被用到了,那么我们可以认为这个对象被泄露了。如果这样的对象数量很多,那么很明显,大量的内存空间就被泄露(“浪费”更准确一些)了。
不过,本文要说的内存泄露,并不属于上述原因,因此打上了引号。其具体原因,确实出乎意料。欲知详情,请看下面讲解。
分析内存泄露的一般步骤
如果发现Java应用程序占用的内存出现了泄露的迹象,那么我们一般采用下面的步骤分析
1. 把Java应用程序使用的heap dump下来
2. 使用Java heap分析工具,找出内存占用超出预期(一般是因为数量太多)的嫌疑对象
3. 必要时,需要分析嫌疑对象和其他对象的引用关系。
4. 查看程序的源代码,找出嫌疑对象数量过多的原因。
dump heap
如果Java应用程序出现了内存泄露,千万别着急着把应用杀掉,而是要保存现场。如果是互联网应用,可以把流量切到其他服务器。保存现场的目的就是为了把运行中JVM的heap dump下来。
JDK自带的jmap工具,可以做这件事情。它的执行方法是:
jmap -dump:format=b,file=heap.bin <pid>
format=b的含义是,dump出来的文件时二进制格式。
file-heap.bin的含义是,dump出来的文件名是heap.bin。
<pid>就是JVM的进程号。
(在linux下)先执行ps aux | grep java,找到JVM的pid;然后再执行jmap -dump:format=b,file=heap.bin <pid>,得到heap dump文件。
analyze heap
将二进制的heap dump文件解析成human-readable的信息,自然是需要专业工具的帮助,这里推荐Memory Analyzer 。
Memory Analyzer,简称MAT,是Eclipse基金会的开源项目,由SAP和IBM捐助。巨头公司出品的软件还是很中用的,MAT可以分析包含数亿级对象的heap、快速计算每个对象占用的内存大小、对象之间的引用关系、自动检测内存泄露的嫌疑对象,功能强大,而且界面友好易用。
MAT的界面基于Eclipse开发,以两种形式发布:Eclipse插件和Eclipe RCP。MAT的分析结果以图片和报表的形式提供,一目了然。总之个人还是非常喜欢这个工具的。下面先贴两张官方的screenshots:
MAT的分析结果概述
MAT分析对象的大小及数量
言归正传,我用MAT打开了heap.bin,很容易看出,char[]的数量出其意料的多,占用90%以上的内存 。一般来说,char[]在JVM确实会占用很多内存,数量也非常多,因为String对象以char[]作为内部存储。但是这次的char[]太贪婪了,仔细一观察,发现有数万计的char[],每个都占用数百K的内存 。这个现象说明,Java程序保存了数以万计的String 大 对象 。结合程序的逻辑,这个是不应该的,肯定在某个地方出了问题。
顺藤摸瓜
在可疑的char[]中,任意挑了一个,使用Path To GC Root功能,找到该char[]的引用路径,发现String对象是被一个HashMap中引用的 。这个也是意料中的事情,Java的内存泄露多半是因为对象被遗留在全局的HashMap中得不到释放。不过,该HashMap被用作一个缓存,设置了缓存条目的阈值,到达到阈值后会自动淘汰。从这个逻辑分析,应该不会出现内存泄露的。虽然缓存中的String对象已经达到数万计,但仍然没有达到预先设置的阈值(阈值设置地比较大,因为当时预估String对象都比较小)。
但是,另一个问题引起了我的注意:为什么缓存的String对象如此巨大?内部char[]的长度达数百K。虽然缓存中的String对象数量还没有达到阈值,但是String对象大小远远超出了我们的预期,最终导致内存被大量消耗,形成内存泄露的迹象(准确说应该是内存消耗过多) 。
就这个问题进一步顺藤摸瓜,看看String大对象是如何被放到HashMap中的。通过查看程序的源代码,我发现,确实有String大对象,不过并没有把String大对象放到HashMap中,而是把String大对象进行split(调用String.split方法),然后将split出来的String小对象放到HashMap中 了。
这就奇怪了,放到HashMap中明明是split之后的String小对象,怎么会占用那么大空间呢?难道是String类的split方法有问题?
查看代码
带着上述疑问,我查阅了Sun JDK6中String类的代码,主要是是split方法的实现:
view plaincopy to clipboardprint?
1. public String[] split(String regex, int limit) {
2. return Pattern.compile(regex).split(this, limit);
3. }
public String[] split(String regex, int limit) { return Pattern.compile(regex).split(this, limit); }
可以看出,Stirng.split方法调用了Pattern.split方法。继续看Pattern.split方法的代码:
view plaincopy to clipboardprint?
1. public String[] split(CharSequence input, int limit) {
2. int index = 0;
3. boolean matchLimited = limit > 0;
4. ArrayList<String> matchList = new ArrayList<String>();
5. Matcher m = matcher(input);
6. // Add segments before each match found
7. while(m.find()) {
8. if (!matchLimited || matchList.size() < limit - 1) {
9. String match = input.subSequence(index, m.start()).toString();
10. matchList.add(match);
11. index = m.end();
12. } else if (matchList.size() == limit - 1) { // last one
13. String match = input.subSequence(index,
14. input.length()).toString();
15. matchList.add(match);
16. index = m.end();
17. }
18. }
19. // If no match was found, return this
20. if (index == 0)
21. return new String[] {input.toString()};
22. // Add remaining segment
23. if (!matchLimited || matchList.size() < limit)
24. matchList.add(input.subSequence(index, input.length()).toString());
25. // Construct result
26. int resultSize = matchList.size();
27. if (limit == 0)
28. while (resultSize > 0 && matchList.get(resultSize-1).equals(""))
29. resultSize--;
30. String[] result = new String[resultSize];
31. return matchList.subList(0, resultSize).toArray(result);
32. }
public String[] split(CharSequence input, int limit) { int index = 0; boolean matchLimited = limit > 0; ArrayList<String> matchList = new ArrayList<String>(); Matcher m = matcher(input); // Add segments before each match found while(m.find()) { if (!matchLimited || matchList.size() < limit - 1) { String match = input.subSequence(index, m.start()).toString(); matchList.add(match); index = m.end(); } else if (matchList.size() == limit - 1) { // last one String match = input.subSequence(index, input.length()).toString(); matchList.add(match); index = m.end(); } } // If no match was found, return this if (index == 0) return new String[] {input.toString()}; // Add remaining segment if (!matchLimited || matchList.size() < limit) matchList.add(input.subSequence(index, input.length()).toString()); // Construct result int resultSize = matchList.size(); if (limit == 0) while (resultSize > 0 && matchList.get(resultSize-1).equals("")) resultSize--; String[] result = new String[resultSize]; return matchList.subList(0, resultSize).toArray(result); }
注意看第9行:Stirng match = input.subSequence(intdex, m.start()).toString();
这里的match就是split出来的String小对象,它其实是String大对象subSequence的结果。继续看String.subSequence的代码:
view plaincopy to clipboardprint?
1. public CharSequence subSequence(int beginIndex, int endIndex) {
2. return this.substring(beginIndex, endIndex);
3. }
public CharSequence subSequence(int beginIndex, int endIndex) { return this.substring(beginIndex, endIndex); }
String.subSequence有调用了String.subString,继续看:
view plaincopy to clipboardprint?
1. public String substring(int beginIndex, int endIndex) {
2. if (beginIndex < 0) {
3. throw new StringIndexOutOfBoundsException(beginIndex);
4. }
5. if (endIndex > count) {
6. throw new StringIndexOutOfBoundsException(endIndex);
7. }
8. if (beginIndex > endIndex) {
9. throw new StringIndexOutOfBoundsException(endIndex - beginIndex);
10. }
11. return ((beginIndex == 0) && (endIndex == count)) ? this :
12. new String(offset + beginIndex, endIndex - beginIndex, value);
13. }
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); }
看第11、12行,我们终于看出眉目,如果subString的内容就是完整的原字符串,那么返回原String对象;否则,就会创建一个新的 String对象,但是这个String对象貌似使用了原String对象的char[]。我们通过String的构造函数确认这一点:
view plaincopy to clipboardprint?
1. // Package private constructor which shares value array for speed.
2. String(int offset, int count, char value[]) {
3. this.value = value;
4. this.offset = offset;
5. this.count = count;
6. }
// Package private constructor which shares value array for speed. String(int offset, int count, char value[]) { this.value = value; this.offset = offset; this.count = count; }
为了避免内存拷贝、加快速度,Sun JDK直接复用了原String对象的char[],偏移量和长度来标识不同的字符串内容。也就是说,subString出的来String小对象仍然会指向原String大对象的char[],split也是同样的情况 。这就解释了,为什么HashMap中String对象的char[]都那么大。
原因解释
其实上一节已经分析出了原因,这一节再整理一下:
1. 程序从每个请求中得到一个String大对象,该对象内部char[]的长度达数百K。
2. 程序对String大对象做split,将split得到的String小对象放到HashMap中,用作缓存。
3. Sun JDK6对String.split方法做了优化,split出来的Stirng对象直接使用原String对象的char[]
4. HashMap中的每个String对象其实都指向了一个巨大的char[]
5. HashMap的上限是万级的,因此被缓存的Sting对象的总大小=万*百K=G级。
6. G级的内存被缓存占用了,大量的内存被浪费,造成内存泄露的迹象。
解决方案
原因找到了,解决方案也就有了。split是要用的,但是我们不要把split出来的String对象直接放到HashMap中,而是调用一下String的拷贝构造函数String(String original),这个构造函数是安全的,具体可以看代码:
view plaincopy to clipboardprint?
1. /**
2. * Initializes a newly created {@code String} object so that it represents
3. * the same sequence of characters as the argument; in other words, the
4. * newly created string is a copy of the argument string. Unless an
5. * explicit copy of {@code original} is needed, use of this constructor is
6. * unnecessary since Strings are immutable.
7. *
8. * @param original
9. * A {@code String}
10. */
11. public String(String original) {
12. int size = original.count;
13. char[] originalValue = original.value;
14. char[] v;
15. if (originalValue.length > size) {
16. // The array representing the String is bigger than the new
17. // String itself. Perhaps this constructor is being called
18. // in order to trim the baggage, so make a copy of the array.
19. int off = original.offset;
20. v = Arrays.copyOfRange(originalValue, off, off+size);
21. } else {
22. // The array representing the String is the same
23. // size as the String, so no point in making a copy.
24. v = originalValue;
25. }
26. this.offset = 0;
27. this.count = size;
28. this.value = v;
29. }
/** * Initializes a newly created {@code String} object so that it represents * the same sequence of characters as the argument; in other words, the * newly created string is a copy of the argument string. Unless an * explicit copy of {@code original} is needed, use of this constructor is * unnecessary since Strings are immutable. * * @param original * A {@code String} */ public String(String original) { int size = original.count; char[] originalValue = original.value; char[] v; if (originalValue.length > size) { // The array representing the String is bigger than the new // String itself. Perhaps this constructor is being called // in order to trim the baggage, so make a copy of the array. int off = original.offset; v = Arrays.copyOfRange(originalValue, off, off+size); } else { // The array representing the String is the same // size as the String, so no point in making a copy. v = originalValue; } this.offset = 0; this.count = size; this.value = v; }
只是,new String(string)的代码很怪异,囧。或许,subString和split应该提供一个选项,让程序员控制是否复用String对象的char[]。
是否是Bug
虽然,subString和split的实现造成了现在的问题,但是这能否算String类的bug呢?个人觉得不好说。因为这样的优化是比较合理的,subString和spit的结果肯定是原字符串的连续子序列。只能说,String不仅仅是一个核心类,它对于JVM来说是与原始类型同等重要的类型。
JDK实现对String做各种可能的优化都是可以理解的。但是优化带来了忧患,我们程序员足够了解他们,才能用好他们。
发表评论
-
内存屏障
2010-02-26 11:03 1508处理器的乱序和并发执行 目前的高级处理器,为了提高内部逻辑元 ... -
函数调用堆栈分析
2010-02-26 10:53 1383理解调用栈最重要的两 ... -
mtrace检测内存泄露
2010-02-25 09:50 1090[url] http://math.acadiau.ca/AC ... -
c语言编程之字符串操作
2010-02-25 09:41 8621. //在s串中查找与s1相匹配的字符串,找到后用 ... -
linux C 链接库 so制作及调用[转]
2010-02-24 16:26 2579文章分类:C++编程 [文章作者:陈毓端 若转载请标注原文链 ... -
mtrace的使用
2010-02-24 16:02 1312对于内存溢出之类的麻烦可能大家在编写指针比较多的复杂的程序的时 ... -
单片机的C语言中位操作用法(转
2010-02-24 14:27 2213单片机的C语言中位操作用法 作者:郭天祥 在对单处机进 ... -
Linux下的itoa函数
2010-02-21 17:55 1765上篇文章说到linux需要it ... -
va_list、va_start、va_arg、va_end的原理与使用
2010-02-05 10:34 29031. 概述 由于在C语言中没有函数重载,解 ... -
快速排序(quickSort)
2010-02-04 10:50 8671. #include <stdio.h> ... -
C问题---itoa函数
2010-02-04 10:36 1047------------------------------ ... -
itoa函数及atoi函数
2010-02-04 10:35 1313C语言提供了几个标准库函数,可以将任意类型(整型、长整型、浮点 ... -
结构体零长度数组的作用
2010-02-04 10:21 1375在一些 C 语言编写的代码中,有时可以看到如下定义的结构: ... -
优化C代码常用的几招
2010-02-04 10:14 775性能优化方面永远注意8 ... -
我经常去的网站
2010-02-03 17:53 1623MFC相关网站 www.codeproject.com ht ... -
可重入函数与不可重入函数
2010-02-03 16:35 931原文地址:http://blog.chin ... -
linux线程池及其测试
2010-02-03 16:32 2360/*----------------------------- ... -
哈夫曼编码
2010-02-03 16:26 1315本文描述在网上能够找到的最简单,最快速的哈夫曼编码。本方法不使 ... -
锁无关的(Lock-Free)数据结构——在避免死锁的同时确保线程
2010-01-26 14:47 906http://hi.baidu.com/%5F%E2%64%5 ... -
使用 GNU profiler 来提高代码运行速度
2010-01-26 13:46 783进应用程序的性能是一 ...
相关推荐
弱势群体三重忧患研究论文.doc
社会:被AI接管后的忧患社会:被AI接管后的忧患社会:被AI接管后的忧患社会:被AI接管后的忧患社会:被AI接管后的忧患社会:被AI接管后的忧患社会:被AI接管后的忧患社会:被AI接管后的忧患社会:被AI接管后的忧患...
广外科学技术史高科技时代的人文忧患PPT课件.pptx
2. 《答司马谏议书》:王安石主张改革,认为“承天景命,殷忧启圣”,国家必须根据时代变化进行变革,以应对深重的忧患。 四、经典语录: 1. “载舟覆舟”:人民如同承载船只的水,可以扶持君主,也可以颠覆其统治...
这篇PPT的主题引用了《孟子》中的"生于忧患,死于安乐"的理念,旨在探讨个人成长和成功与面对挑战的关系。孟子是中国古代的一位重要思想家,他的思想对后世儒家文化有着深远影响。《孟子》一书记录了他的哲学观点和...
2. 信息分类:分为思路型、经验型、政策型、忧患型和建议型,覆盖上级政策执行、工作创新、突发问题等多方面。 3. 信息筛选:依据思想性、时效性、广泛性和适用性原则,挑选与中心工作相关且有价值的热点信息。 4. ...
风险预测与忧患深思:人工智能对教育发展的冲击与变革——哲学与伦理的思考.pdf
(2) "然后知生于忧患,而死于安乐也" 表示“这样以后,人们才会明白,忧患(能激励人奋发)使人谋求生存发展,而安逸享乐就会让人萎靡,导致死亡”。 再来看第二部分的孟母教子的故事: 5. 这里解释了两个文言词语...
迷你鱼菜共生系统是一种新型的复合耕作体系,它把水产养殖与蔬菜生产这两种原本完全不同的农耕技术,通过巧妙的生态设计,达到科学的协同共生,实现养鱼不换水而无水质忧患,种菜不施肥而正常成长的生态共生效应。...
南唐词则注入士大夫的忧患之感和生命意识,思想境界较高,语言也比较清新雅丽。 四、其他: 1. 《逍遥游》的艺术特色:运用大量寓言阐发哲学思想。文章结构有“断续”之妙,由哲理思想贯穿全文。语言自由灵活,...
《出师表》是三国时期蜀汉丞相诸葛亮在...总的来说,《出师表》是诸葛亮对刘禅的谆谆教诲,展现了他深沉的忧患意识和坚定的使命感。这篇文章不仅是文学上的瑰宝,也是历史和政治智慧的结晶,至今仍具有深远的启示意义。
3. 忧患意识不强:缺乏长远考虑,迟到者未能认识到持续迟到可能导致的严重后果,如受到纪律处分,甚至影响职业生涯和个人声誉。 改正迟到行为的方法主要包括: 1. 严肃端正态度:重新审视自身行为,与有积极上进...
通过演讲,参与者可以回顾历史,增强忧患意识,同时展现新时代大学生的风采。 一、活动宗旨: 活动的宗旨在于纪念祖国的辉煌历程,加深学生对国家安全和校园安全的理解。活动鼓励学生积极参与,强化自我保护意识,...
1. 效劳意识和忧患意识淡薄:领导干部可能忽视了为人民服务的宗旨,缺乏危机感,导致工作积极性不高。 2. 学习与实际工作脱节:理论学习与实践操作之间存在鸿沟,领导干部未能将所学知识应用到实际工作中。 3. 学习...
企业安全忧患意识演讲稿.docx
俄国列宾的《伏尔加河上的纤夫》反映了劳动人民的悲惨处境,突出了民族的忧患意识。 4.印象画派(19 世纪后半期):法国莫奈的《日出·印象》;荷兰凡高(“扑向太阳的画家”)的《向日葵》;法国塞尚擅长静物,色彩...
通过具体的数字,学生能更直观地感受到黄河问题的严重性,从而引发他们的忧患意识和环保行动的决心。 总的来说,这份教学设计旨在通过探究黄河的历史变迁,引导学生关注自然环境的变化,培养他们的环保意识和社会...
4. "忧患"与"安乐":生活状况的对比。 通过学习这些反义词,学生可以更好地理解词义,同时也能增强他们的语境理解和判断能力。 在学习近义词和反义词的过程中,学生还可以通过造句和对比练习来加深记忆。这不仅...
孟子在战国时期的名言“生于忧患而死于安乐”与此相呼应,揭示了忧患意识对于国家存亡的重要性。 【甲骨文研究的重要性】 甲骨文作为我国最早的文字,是了解殷商历史文化的重要窗口。通过研究甲骨文中的灾祸字,如...
2. **增强忧患意识**:了解石化行业的高风险性,始终保持警惕,避免因疏忽导致的违规行为。 3. **建立主人翁意识**:视企业如家,增强员工对企业的归属感,提升工作积极性和责任感,从而减少违纪现象。 这次学习...