提高 Java 代码性能的各种技巧.docx
- 文档编号:11742007
- 上传时间:2023-03-31
- 格式:DOCX
- 页数:13
- 大小:20.66KB
提高 Java 代码性能的各种技巧.docx
《提高 Java 代码性能的各种技巧.docx》由会员分享,可在线阅读,更多相关《提高 Java 代码性能的各种技巧.docx(13页珍藏版)》请在冰豆网上搜索。
提高Java代码性能的各种技巧
提高Java代码性能的各种技巧
2015-05-06 分类:
JAVA开发、编程开发、首页精华暂无人评论 来源:
字节技术
分享到:
更多4
欢迎分享开发问题到码农社区!
将问题帖子私信@程序员俱乐部,将有近60000人为您解答!
Java6,7,8中的String.intern–字符串池
这篇文章将要讨论Java6中是如何实现 String.intern 方法的,以及这个方法在Java7以及Java8中做了哪些调整。
字符串池
字符串池(有名字符串标准化)是通过使用唯一的共享 String 对象来使用相同的值不同的地址表示字符串的过程。
你可以使用自己定义的 Map
很多标准禁止在Java6中使用 String.intern() 因为如果频繁使用池会市区控制,有很大的几率触发 OutOfMemoryException。
OracleJava7对字符串池做了很多改进,你可以通过以下地址进行了解
Java6中的String.intern()
在美好的过去所有共享的String对象都存储在PermGen中—堆中固定大小的部分主要用于存储加载的类对象和字符串池。
除了明确的共享字符串,PermGen字符串池还包含所有程序中使用过的字符串(这里要注意是使用过的字符串,如果类或者方法从未加载或者被条用,在其中定义的任何常量都不会被加载)
Java6中字符串池的最大问题是它的位置—PermGen。
PermGen的大小是固定的并且在运行时是无法扩展的。
你可以使用 -XX:
MaxPermSize=N 配置来调整它的大小。
据我了解,对于不同的平台默认的PermGen大小在32M到96M之间。
你可以扩展它的大小,不过大小使用都是固定的。
这个限制需要你在使用 String.intern 时需要非常小心—你最好不要使用这个方法intern任何无法控制的用户输入。
这是为什么在JAVA6中大部分使用手动管理 Map 来实现字符串池
Java7中的String.intern()
Java7中Oracle的工程师对字符串池的逻辑做了很大的改变—字符串池的位置被调整到heap中了。
这意味着你再也不会被固定的内存空间限制了。
所有的字符串都保存在堆(heap)中同其他普通对象一样,这使得你在调优应用时仅需要调整堆大小。
这个改动使得我们有足够的理由让我们重新考虑在Java7中使用String.intern()。
字符串池中的数据会被垃圾收集
没错,在JVM字符串池中的所有字符串会被垃圾收集,如果这些值在应用中没有任何引用。
这是用于所有版本的Java,这意味着如果 interned的字符串在作用域外并且没有任何引用—它将会从JVM的字符串池中被垃圾收集掉。
因为被重新定位到堆中以及会被垃圾收集,JVM的字符串池看上去是存放字符串的合适位置,是吗?
理论上是—违背使用的字符串会从池中收集掉,当外部输入一个字符传且池中存在时可以节省内存。
看起来是一个完美的节省内存的策略?
在你回答这个之前,可以肯定的是你需要知道字符串池是如何实现的。
在Java6,7,8中JVM字符串池的实现
字符串池是使用一个拥有固定容量的 HashMap 每个元素包含具有相同hash值的字符串列表。
一些实现的细节可以从Javabug报告中获得
默认的池大小是1009(出现在上面提及的bug报告的源码中,在Java7u40中增加了)。
在JAVA6早期版本中是一个常量,在随后的 java6u30至java6u41中调整为可配置的。
而在java7中一开始就是可以配置的(至少在java7u02中是可以配置的)。
你需要指定参数 -XX:
StringTableSize=N, N是字符串池 Map 的大小。
确保它是为性能调优而预先准备的大小。
在Java6中这个参数没有太多帮助,因为你仍任被限制在固定的PermGen内存大小中。
后续的讨论将直接忽略Java6
Java7(直至Java7u40)
在Java7中,换句话说,你被限制在一个更大的堆内存中。
这意味着你可以预先设置好String池的大小(这个值取决于你的应用程序需求)。
通常说来,一旦程序开始内存消耗,内存都是成百兆的增长,在这种情况下,给一个拥有100万字符串对象的字符串池分配8-16M的内存看起来是比较适合的(不要使用1,000,000作为 -XX:
StringTaleSize 的值–它不是质数;使用 1,000,003代替)
你可能期待关于String在Map中的分配—可以阅读我之前关于HashCode方法调优的经验。
你必须设置一个更大的 -XX:
StringTalbeSize 值(相比较默认的1009),如果你希望更多的使用String.intern()—否则这个方法将很快递减到0(池大小)。
我没有注意到在intern小于100字符的字符串时的依赖情况(我认为在一个包含50个重复字符的字符串与现实数据并不相似,因此100个字符看上去是一个很好的测试限制)
下面是默认池大小的应用程序日志:
第一列是已经intern的字符串数量,第二列intern10,000个字符串所有的时间(秒)
0;time=0.0sec
50000;time=0.03sec
100000;time=0.073sec
150000;time=0.13sec
200000;time=0.196sec
250000;time=0.279sec
300000;time=0.376sec
350000;time=0.471sec
400000;time=0.574sec
450000;time=0.666sec
500000;time=0.755sec
550000;time=0.854sec
600000;time=0.916sec
650000;time=1.006sec
700000;time=1.095sec
750000;time=1.273sec
800000;time=1.248sec
850000;time=1.446sec
900000;time=1.585sec
950000;time=1.635sec
1000000;time=1.913sec
测试是在Corei5-3317U@1.7GhzCPU设备上进行的。
你可以看到,它成线性增长,并且在JVM字符串池包含一百万个字符串时,我仍然可以近似每秒 intern 5000个字符串,这对于在内存中处理大量数据的应用程序来说太慢了。
现在,调整 -XX:
StringTableSize=100003 参数来重新运行测试:
50000;time=0.017sec
100000;time=0.009sec
150000;time=0.01sec
200000;time=0.009sec
250000;time=0.007sec
300000;time=0.008sec
350000;time=0.009sec
400000;time=0.009sec
450000;time=0.01sec
500000;time=0.013sec
550000;time=0.011sec
600000;time=0.012sec
650000;time=0.015sec
700000;time=0.015sec
750000;time=0.01sec
800000;time=0.01sec
850000;time=0.011sec
900000;time=0.011sec
950000;time=0.012sec
1000000;time=0.012sec
可以看到,这时插入字符串的时间近似于常量(在Map的字符串列表中平均字符串个数不超过10个),下面是相同设置的结果,不过这次我们将向池中插入1000万个字符串(这意味着Map中的字符串列表平均包含100个字符串)
2000000;time=0.024sec
3000000;time=0.028sec
4000000;time=0.053sec
5000000;time=0.051sec
6000000;time=0.034sec
7000000;time=0.041sec
8000000;time=0.089sec
9000000;time=0.111sec
10000000;time=0.123sec
现在让我们将吃的大小增加到100万(精确的说是1,000,003)
1000000;time=0.005sec
2000000;time=0.005sec
3000000;time=0.005sec
4000000;time=0.004sec
5000000;time=0.004sec
6000000;time=0.009sec
7000000;time=0.01sec
8000000;time=0.009sec
9000000;time=0.009sec
10000000;time=0.009sec
如你所看到的,时间非常平均,并且与“0到100万”的表没有太大差别。
甚至在池大小足够大的情况下,我的笔记本也能每秒添加1,000,000个字符对象。
我们还需要手工管理字符串池吗?
现在我们需要对比JVM字符串池和 WeakHashMap
下面的方法用来替换 String.intern:
privatestaticfinalWeakHashMap
newWeakHashMap
privatestaticStringmanualIntern(finalStringstr)
{
finalWeakReference
if(cached!
=null)
{
finalStringvalue=cached.get();
if(value!
=null)
returnvalue;
}
s_manualCache.put(str,newWeakReference
returnstr;
}
下面针对手工池的相同测试:
0;manualtime=0.001sec
50000;manualtime=0.03sec
100000;manualtime=0.034sec
150000;manualtime=0.008sec
200000;manualtime=0.019sec
250000;manualtime=0.011sec
300000;manualtime=0.011sec
350000;manualtime=0.008sec
400000;manualtime=0.027sec
450000;manualtime=0.008sec
500000;manualtime=0.009sec
550000;manualtime=0.008sec
600000;manualtime=0.008sec
650000;manualtime=0.008sec
700000;manualtime=0.008sec
750000;manualtime=0.011sec
800000;manualtime=0.007sec
850000;manualtime=0.008sec
900000;manualtime=0.008sec
950000;manualtime=0.008sec
1000000;manualtime=0.008sec
当JVM有足够内存时,手工编写的池提供了良好的性能。
不过不幸的是,我的测试(保留 String.valueOf(0 JVM字符串池(size=1,000,003)从另一方面讲在JVM内存足够时提供了相同的性能特性,知道JVM字符串池包含12.72M的字符串并消耗掉所有内存(5倍多)。 我认为,这非常值得你在你的应用中去掉所有手工字符串池。 在Java7u40+以及Java8中的String.intern() Java7u40版本扩展了字符串池的大小(这是组要的性能更新)到60013.这个值允许你在池中包含大约30000个独立的字符串。 通常来说,这对于需要保存的数据来说已经足够了,你可以通过 -XX: +PrintFlagsFinal JVM参数获得这个值。 我尝试在原始发布的Java8中运行相同的测试,Java8仍然支持 -XX: StringTableSize 参数来兼容Java7特性。 主要的区别在于Java8中默认的池大小增加到60013: 50000;time=0.019sec 100000;time=0.009sec 150000;time=0.009sec 200000;time=0.009sec 250000;time=0.009sec 300000;time=0.009sec 350000;time=0.011sec 400000;time=0.012sec 450000;time=0.01sec 500000;time=0.013sec 550000;time=0.013sec 600000;time=0.014sec 650000;time=0.018sec 700000;time=0.015sec 750000;time=0.029sec 800000;time=0.018sec 850000;time=0.02sec 900000;time=0.017sec 950000;time=0.018sec 1000000;time=0.021sec 测试代码 这篇文章的测试代码很简单,一个方法中循环创建并保留新字符串。 你可以测量它保留10000个字符串所需要的时间。 最好配合 -verbose: gc JVM参数来运行这个测试,这样可以查看垃圾收集是何时以及如何发生的。 另外最好使用 -Xmx 参数来执行堆的最大值。 这里有两个测试: testStringPoolGarbageCollection 将显示JVM字符串池被垃圾收集—检查垃圾收集日志消息。 在Java6的默认PermGen大小配置上,这个测试会失败,因此最好增加这个值,或者更新测试方法,或者使用Java7. 第二个测试显示内存中保留了多少字符串。 在Java6中执行需要两个不同的内存配置比如: -Xmx128M 以及 -Xmx1280M (10倍以上)。 你可能发现这个值不会影响放入池中字符串的数量。 另一方面,在Java7中你能够在堆中填满你的字符串。 /** -TestingString.intern. * -Runthisclassatleastwith-verbose: gcJVMparameter. */ publicclassInternTest{ publicstaticvoidmain(String[]args){ testStringPoolGarbageCollection(); testLongLoop(); } /** -Usethismethodtoseewhereinternedstringsarestored -andhowmanyofthemcanyoufitforthegivenheapsize. */ privatestaticvoidtestLongLoop() { test(1000*1000*1000); //uncommentthefollowinglinetoseethehand-writtencacheperformance //testManual(1000*1000*1000); } /** -Usethismethodtocheckthatnotusedinternedstringsaregarbagecollected. */ privatestaticvoidtestStringPoolGarbageCollection() { //firstmethodcall-useitasareference test(1000*1000); //wearegoingtocleanthecachehere. System.gc(); //checkthememoryconsumptionandhowlongdoesittaketointernstrings //inthesecondmethodcall. test(1000*1000); } privatestaticvoidtest(finalintcnt) { finalList longstart=System.currentTimeMillis(); for(inti=0;i { finalStringstr="Verylongteststring,whichtellsyouaboutsomething"+ "very-veryimportant,definitelydeservingtobeinterned#"+i; //uncommentthefollowinglinetotestdependencyfromstringlength //finalStringstr=Integer.toString(i); lst.add(str.intern()); if(i%10000==0) { System.out.println(i+";time="+(System.currentTimeMillis()-start)/1000.0+"sec"); start=System.currentTimeMillis(); } } System.out.println("Totallength="+lst.size()); } privatestaticfinalWeakHashMap newWeakHashMap privatestaticStringmanualIntern(finalStringstr) { finalWeakReference if(cached! =null) { finalStringvalue=cached.get(); if(value! =null) returnvalue; } s_manualCache.put(str,newWeakReference returnstr; } privatestaticvoidtestManual(finalintcnt) { finalList longstart=System.currentTimeMillis(); for(inti=0;i { finalStringstr="Verylongteststring,whichtellsyouaboutsomething"+ "very-veryimportant,definitelydeservingtobeinterned#"+i; lst.add(manualIntern(str)); if(i%10000==0) { System.out.println(i+";manualtime="+(System.currentTimeMillis()-start)/1000.0+"sec"); start=System.currentTimeMillis(); } } System.out.println("Totallength="+
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- 提高 Java 代码性能的各种技巧 代码 性能 各种 技巧