在我们的一个Java应用程序中,我们看到频繁的Full GCs。每秒2次。
大多数堆空间> 90%归因于char数组分配。
在堆转储分析中,发现所有都在GC根目录下无法访问,但似乎垃圾收集器似乎没有收集。 char数组仅在我们的代码中的一个位置用于IO的方法范围。
我们有默认的年轻到终身比率(1:3)和默认的GC算法
我没有改变这一点,因为这个应用程序在1G堆上运行很长时间了,现在突然连4G也不够用。
我最大的痛点是分配给char数组的90%终身内存,应该是ZERO。有没有人知道这里可能出现什么问题或者如何进一步调试呢? 我似乎空白了
更新1:
在堆分析期间,我可以看到最大的char数组,因此指出了正在分配这些数组的代码。我们正在尝试从大小为100K的DB中读取clobs,而在堆转储中,发现相同的数组大小约为100MB [我仍在研究这个问题。如果我找到任何东西会更新]
我之所以说这些数组是无法访问的,因为我可以在GC根目录下看到它们,在无法访问的头下。如果假设有任何错误,请纠正我。
这是用于从DB读取clobs的代码。
String readFromRSClobStream(Reader reader, int buffersize) throws Exception {
String result="";
char[] buffer=new char[buffersize];
int count=0;
while((count=reader.read(buffer))!=-1)
{
String buffer_str=new String(buffer);
result+=buffer_str.substring(0,count);
}
return result;
}
String readFromRSClobStream(Reader reader, int buffersize) throws Exception {
String result="";
char[] buffer=new char[buffersize];
int count=0;
while((count=reader.read(buffer))!=-1)
{
String buffer_str=new String(buffer);
result+=buffer_str.substring(0,count);
}
return result;
}
泄密嫌疑人观点给了我这个
char []和String都与上面代码中的一个有关。
附:
希望我可以分享完整的hprof
答案 0 :(得分:2)
我可以想到三种可能的解释:
您的堆转储分析错误,并且数组实际上是可以访问的。
这些大字符数组通常可以通过软引用或弱引用来访问,并且有一些"定相"效果在这里发生。 (通常需要2个或更多GC循环来回收软/弱引用所引用的对象。如果在之后查看堆,GC已经破坏了引用,它将包含所有以前引用的对象...无法访问。
您正在以过高的速率分配阵列和/或其中一些阵列非常大。高比率的对象分配通常会导致大量的年轻空间集合。但是,如果你分配"非常大"对象(或数组),某些JVM上的分配器会将新对象直接放入终身空间。太多可能会引发频繁的完整GC。
(当然,它可能完全不同,但你还没有给我们很多证据可供使用。)
参考文献:"Size Matters"作者:Jon Masamitsu的@ Oracle。
<强>更新强>
您的readFromRSClobStream
方法可能效率低下并且会产生大量垃圾,尤其是bufferSize
相对于CLOB大小较小时。每次执行字符串连接时,都会创建一个新的String,并将旧String的内容和buffer
中的字符复制到其中。这会为您提供O(N^2)
个复制的字符和O(N)
对象分配。
因此,如果您正在阅读的CLOB非常大,那么您将分配许多非常大的char[]
个对象,这就是(IMO)可能导致GC消化不良的原因。
更有效的方法是这样的:
String readFromRSClobStream(Reader reader, int clobSizeHint, int bufferSize)
throws IOException {
StringBuilder sb = new StringBuilder(clobSizeHint);
char[] buffer = new char[buffersize];
int count = 0;
while ((count = reader.read(buffer)) != -1) {
sb.append(buffer, 0, count);
}
return sb.toString;
}
如果大小提示正确(即它是以字符表示的实际CLOB大小),则会为您提供O(N)
个复制的字符和O(1)
对象分配。
即使大小提示不准确(但不是太大),您也会获得O(N)
个复制字符和O(logN)
分配或更好的分配。 (这是StringBuilder
使用的策略,每次填充时至少将内部缓冲区大小加倍。)