Permgen OOM分析簡介(如何獲取String.intern內容)
Permgen OOM分析簡介(如何獲取String.intern內容)
一、常用的分析Permgen OOM的方式:
一般出現Permgen的OutOfMemory記憶體溢位,上網搜尋解決方式,一般給出的建議都是增大Permgen的記憶體配置,如-XX:MaxPermSize=128m。
但其實程式本身可能就存在不合理的地方。
Permgen包含2部分資料,一部分是載入的class,一部分是String.intern即常量池快取。
1、 分析載入的class
可以使用MemoryAnalyzer工具擷取Heap,把其中的所有class記錄下來,然後寫一個簡單的載入類程式碼(Class.forname)載入所有記錄的class,然後用jconsole看一下Permgen的大小,即可知道載入的class所佔用的大小。
2、 使用工具jmap –permstat也可以檢視(但必須是jdk1.6.0_31以後的版本)
$ jmap -permstat 543
Attaching to process ID 543, please wait…
Debugger attached successfully.
Server compiler detected.
JVM version is 19.1-b02-334
14584 intern Strings occupying 1603648 bytes.
finding class loader instances ..Warning: skipping invalid TLAB for thread
3、 分析載入的String.intern()常量池的記憶體大小
工具jmap –permstat可以檢視其大小,但如果要看String的具體內容,可以用jdk安裝目錄/lib/sa-jdi.jar(Serviceability Agent)來檢視。jstack、jmap等工具在使用-F引數啟動時其實就是通過SA來實現功能的。
二、SA簡介:
1、HotSpot有一套私有API提供了對JVM內部資料結構的審視功能,稱為Serviceability Agent。它是一套Java API,雖然HotSpot是用C++寫的,但SA提供了HotSpot中重要資料結構的Java映象類,所以可以直接寫Java程式碼來檢視一個跑在HotSpot上的Java程序的內部狀態。它也提供了一些封裝好的工具,可以直接在命令列上跑。
SA的一個重要特徵是它是“程序外審視工具”。也就是說,SA並不執行在要審視的目標程序中,而是執行在一個獨立的Java程序中,通過作業系統上提供的除錯API來連線到目標程序上。這樣,SA的執行不會受到目標程序狀態的影響,因而可以用於審視一個已經掛起的Java程序,或者是core dump檔案。當然,這也就意味這一個SA程序不能用於審視自己。
一個被偵錯程式連線上的程序會被暫停下來。所以在SA連線到目標程序時,目標程序也是一直處於暫停狀態的,直到SA解除連線。如果需要在線上使用SA的話需要小心,不要通過SA做過於耗時的分析,寧可先把資料都抓出來,把SA的連線解除掉之後再離線分析。
2、使用方式:
java -classpath .;..\lib\sa-jdi.jar demo.PrintStringTable <PID>
3、java -cp %JAVA_HOME%\lib\sa-jdi.jar sun.jvm.hotspot.HSDB
可以開啟一個介面attach到程序
4、CLHSDB使用
F:\DevelopTool\Java\jdk1.7.0_07\bin>java -classpath .;F:\DevelopTool\Java\jdk1.7.0_07\lib\sa-jdi.jar sun.jvm.hotspot.CLHSDB
hsdb> help
Available commands:
assert true | false
attach pid | exec core
class name
classes
detach
dis address [ length ]
dumpcfg { -a | id }
dumpclass { address | name } [ directory ]
dumpcodecache
dumpheap [ file ]
dumpideal { -a | id }
dumpilt { -a | id }
echo [ true | false ]
examine [ address/count ] | [ address,address]
field [ type [ name fieldtype isStatic offset address ] ]
findpc address
flags [ flag | -nd ]
help [ command ]
history
inspect expression
intConstant [ name [ value ] ]
jdis address
jhisto
jseval script
jsload file
jstack [-v]
livenmethods
longConstant [ name [ value ] ]
mem address [ length ]
pmap
print expression
printas type expression
printmdo [ -a | expression ]
printstatics [ type ]
pstack [-v]
quit
reattach
revptrs address
scanoops start end [ type ]
search [ heap | perm | rawheap | codecache | threads ] value
source filename
symbol address
symboldump
symboltable name
sysprops
thread { -a | id }
threads
tokenize …
type [ type [ name super isOop isInteger isUnsigned size ] ]
universe
verbose true | false
versioncheck [ true | false ]
vmstructsdump
whatis address
where { -a | id }
hsdb>
其中常用的有classes檢視程序所有載入的類,flags檢視程序的所有的JVM引數,sysprops檢視程序的所有的系統屬性,universe檢視程序記憶體使用
5、jconsole jstack jmap jinfo用到了Dynamic Attach
jinfo(-flags and -sysprops), jmap(-F –permstat), jstack(-F)用到了SA
6、但JDK6在windows上用SA會丟擲異常:
Attaching to process ID 8980, please wait…
Error attaching to process:
Timed out while attempting to connect to debug server (please start SwDbgSrv.exe).
目前(JDK6)在Windows上SA沒有隨HotSpot一起釋出,所以無法在Windows上使用;在Linux、Solaris、Mac上使用都沒問題。從JDK7 build 64開始Windows版JDK也帶上SA。
三、獲取Permgen中的String.intern的內容:
1、使用以下程式碼獲取Permgen的String常量池內容
public class PrintStringTable extends Tool {
public PrintStringTable() {
}
class StringPrinter implements StringTable.StringVisitor {
private OopField stringValueField;
public StringPrinter() {
VM vm = VM.getVM();
SystemDictionary sysDict = vm.getSystemDictionary();
InstanceKlass strKlass = sysDict.getStringKlass();
stringValueField = (OopField) strKlass.findField(“value”, “[C”);
}
@Override
public void visit(Instance instance) {
TypeArray charArray = ((TypeArray)stringValueField.getValue(instance));
StringBuilder sb = new StringBuilder();
for(long i=0;i<charArray.getLength();i++) {
sb.append(charArray.getCharAt(i));
}
System.out.println(“Address: ” + instance.getHandle() + ” Content: ” + sb.toString());
//System.out.println(sb.toString());
}
}
public static void main(String args[]) throws Exception {
if(args.length == 0 || args.length > 1) {
System.err.println(“Usage: PrintStringTable <PID of the JVM whose string table you want to print>”);
System.exit(1);
}
PrintStringTable pst = new PrintStringTable();
pst.start(args);
pst.stop();
}
@Override
public void run() {
StringTable table = VM.getVM().getStringTable();
table.stringsDo(new StringPrinter());
}
}
2、例子程式:
package demo;
public class TestStrIntern
{
static String str = “staticstr”;
String str1;
private String createString()
{
char[] array = new char[10];
for(int i=9;i>=0;i–)
{
array[i] = (char)i;
}
// new String(array);
// return new String(“9876543210”);
return new StringBuilder().append(“abc12abc”).append(“223cba223”).toString();
}
public static void main(String[] args)
{
TestStrIntern obj = new TestStrIntern();
obj.str1 = “nonintern11122334455”;
new String(“123123123”).intern();
new String(“tmp3312334”).intern();
new String(“1231241234124324231446536567”).intern();
new String(“nonintern123421”);
new String(“nonintern122345546”);
String str2 = obj.createString();
System.out.println(str2);
try
{
Thread.sleep(30*60*1000);
}
catch (InterruptedException e)
{
e.printStackTrace();
}
}
}
3、執行java -classpath .;..\lib\sa-jdi.jar demo.PrintStringTable <PID>,將會列印abc12abc和223cba223,但不會列印abc12abc223cba223:
ddress: 0x22cc0260 Content: abc12abc
Address: 0x37b99860 Content: ([Ljava/lang/String;)V
Address: 0x37b9d070 Content: ISO_8859_13
Address: 0x37ba0f70 Content: dispatchMouseWheelToAncestor
Address: 0x37ba3bc8 Content: Could not access Graphics Environment:
Address: 0x37ba7ff0 Content: ScaledBlit(…)
Address: 0x37badbd0 Content: font-style: italic ;
Address: 0x37bb3018 Content: MenuBar.background
Address: 0x37bb6138 Content: SplitPane.rightButton.textAndMnemonic
Address: 0x22cc0298 Content: 223cba223
Address: 0x22b63f38 Content: IBM949C
Address: 0x37b9a598 Content: char
Address: 0x37b9b1f8 Content: 437
Address: 0x37ba5c68 Content: ,keyChar=
Address: 0x37baf5e0 Content: List.focusInputMap