西海岸より

つらつらざつざつと

javaプロセスの使用メモリの合計値を出力

macjps、jmapコマンドを使用しようとシェルを作ったのでメモ。

javaでは標準でjmapというコマンドがあり、メモリリークが発生しているかを調べるのに役立つ。起動中のjavaプロセスを指定することで、そのプロセス内でどのようなオブジェクトへの参照、またそれぞれがどれだけのメモリ容量を使用しているか見る事ができる。

一般的な使い方は、以下。

  • jpsコマンドでプロセスを確認
$ jps
43734 Jps
43733 HelloWorld
  • jmapコマンドでメモリの参照状況を表示
$ jmap -histo 43733
Attaching to process ID 43733, please wait...
Debugger attached successfully.
Client compiler detected.
JVM version is 1.5.0_16-133
Iterating over heap. This may take a while...
Warning: skipping invalid TLAB for thread t@62979
Warning: skipping invalid TLAB for thread t@63235
Warning: skipping invalid TLAB for thread t@63491
Warning: skipping invalid TLAB for thread t@63747
Object Histogram:

Size	Count	Class description
-------------------------------------------------------
16280416	9800	char[]
2985776	26272	* ConstMethodKlass
1500072	39679	* SymbolKlass
1477288	26272	* MethodKlass
1244088	2032	* ConstantPoolKlass
918712	2032	* InstanceKlassKlass
913328	2032	* ConstantPoolCacheKlass
371552	2303	byte[]
237352	3231	short[]
235032	9793	java.lang.String
202560	2110	java.lang.Class
170016	3171	java.lang.Object[]
92088	1670	int[]
12160	40	* ObjArrayKlassKlass
9512	233	java.lang.Object[]
4152	173	java.util.LinkedList$Entry
2632	47	java.net.URL
2608	27	java.util.HashMap$Entry[]
2432	8	* TypeArrayKlassKlass
2112	88	java.util.HashMap$Entry
1536	64	java.util.Hashtable$Entry
1464	61	java.util.LinkedList
1200	10	* KlassKlass
1152	33	java.lang.String[]
1000	25	java.util.HashMap
960	30	java.util.LinkedHashMap$Entry
952	17	java.nio.DirectByteBuffer
952	17	java.util.jar.JarFile
928	29	java.lang.ref.Finalizer
864	12	java.lang.reflect.Field
864	9	java.util.jar.JarFile$JarFileEntry
832	13	java.lang.reflect.Constructor
736	8	java.util.Hashtable$Entry[]
704	22	java.util.Locale
680	17	sun.misc.Cleaner
672	28	java.io.ExpiringCache$Entry
608	2	* ArrayKlassKlass
600	25	java.util.Vector
544	17	sun.misc.URLClassPath$JarLoader
384	12	java.io.ObjectStreamField
384	4	java.lang.Thread
320	8	java.util.zip.Inflater
280	7	java.util.Hashtable
272	17	java.util.zip.ZipFile$ZipCloser
264	12	java.lang.Class[]
224	7	java.lang.ref.SoftReference
168	7	java.lang.OutOfMemoryError
144	7	java.io.ObjectStreamField[]
120	5	sun.misc.JarIndex
120	15	java.lang.Object
120	5	java.util.ArrayList
112	2	sun.nio.cs.StreamEncoder$CharsetSE
112	2	java.io.ExpiringCache$1
96	3	java.lang.ref.ReferenceQueue
96	2	java.lang.ThreadGroup
96	1	java.lang.ref.Finalizer$FinalizerThread
96	2	java.nio.HeapByteBuffer
96	1	java.lang.ref.Reference$ReferenceHandler
96	2	sun.nio.cs.UTF_8$Encoder
96	5	java.lang.reflect.Constructor[]
80	1	java.lang.reflect.Method
80	1	java.lang.ThreadLocal$ThreadLocalMap$Entry[]
80	2	java.io.ExpiringCache
80	1	sun.misc.Launcher$ExtClassLoader
80	2	java.io.BufferedWriter
72	1	sun.misc.Launcher$AppClassLoader
72	3	sun.reflect.NativeConstructorAccessorImpl
72	3	java.util.Stack
72	3	java.security.AccessControlContext
72	2	java.lang.reflect.Field[]
72	3	java.lang.RuntimePermission
64	4	java.io.File
64	2	sun.misc.URLClassPath
64	4	java.lang.annotation.Annotation[]
64	2	java.io.PrintStream
64	2	java.lang.ref.ReferenceQueue$Null
64	4	sun.reflect.DelegatingConstructorAccessorImpl
64	2	java.lang.Thread[]
56	1	sun.reflect.DelegatingClassLoader
48	2	java.io.BufferedOutputStream
48	2	sun.nio.cs.Surrogate$Parser
48	2	java.io.FileOutputStream
48	3	java.nio.charset.CodingErrorAction
48	1	java.util.Properties
48	2	java.lang.ref.SoftReference[]
48	1	long[]
48	2	java.lang.ref.WeakReference
48	2	java.io.OutputStreamWriter
48	3	java.io.FileDescriptor
48	3	sun.misc.Signal
48	3	java.util.HashSet
48	3	java.lang.Integer
40	1	sun.nio.cs.StandardCharsets$Classes
40	1	sun.nio.cs.StandardCharsets$Cache
40	1	sun.nio.cs.StandardCharsets$Aliases
40	5	java.lang.ref.ReferenceQueue$Lock
32	1	java.io.FilePermission
32	2	sun.misc.NativeSignalHandler
32	1	java.util.Collections$SynchronizedMap
32	1	java.io.BufferedInputStream
32	1	java.util.concurrent.atomic.AtomicReferenceFieldUpdater$AtomicReferenceFieldUpdaterImpl
32	1	java.lang.OutOfMemoryError[]
32	2	java.nio.charset.CoderResult
32	1	java.lang.ThreadGroup[]
32	1	sun.misc.SoftCache
32	1	java.lang.ThreadLocal$ThreadLocalMap$Entry
32	1	java.lang.ClassLoader$NativeLibrary
32	2	java.lang.ThreadLocal
32	1	java.util.Random
32	1	java.security.ProtectionDomain
32	2	java.lang.Boolean
32	2	java.nio.ByteOrder
32	1	java.security.CodeSource
24	1	java.lang.ArithmeticException
24	1	sun.nio.cs.StandardCharsets
24	1	java.lang.ThreadLocal$ThreadLocalMap
24	1	java.lang.ref.SoftReference[][]
24	1	sun.nio.cs.UTF_8
24	1	java.lang.NullPointerException
24	1	java.io.File[]
24	1	java.io.UnixFileSystem
24	1	java.security.BasicPermissionCollection
24	1	java.lang.reflect.ReflectPermission
24	1	java.security.Permissions
16	1	sun.misc.URLClassPath$FileLoader
16	1	java.util.BitSet
16	1	java.lang.StringBuilder
16	1	java.nio.charset.CoderResult$2
16	2	sun.net.www.protocol.jar.Handler
16	1	java.nio.charset.CoderResult$1
16	1	sun.misc.Launcher
16	1	java.security.cert.Certificate[]
16	1	sun.reflect.BootstrapConstructorAccessorImpl
16	1	java.lang.StackTraceElement[]
16	1	java.security.Principal[]
16	1	java.util.concurrent.atomic.AtomicLong
16	1	java.util.Collections$EmptyList
16	1	java.io.FilePermissionCollection
16	1	java.io.FileInputStream
16	1	java.util.Collections$EmptyMap
8	1	java.lang.System$2
8	1	java.lang.Terminator$1
8	1	java.util.Collections$EmptySet
8	1	sun.reflect.ReflectionFactory
8	1	java.lang.ref.Reference$Lock
8	1	java.util.jar.JavaUtilJarAccessImpl
8	1	sun.misc.Launcher$Factory
8	1	java.lang.String$CaseInsensitiveComparator
8	1	java.lang.Runtime
8	1	java.lang.reflect.ReflectAccess
8	1	java.net.UnknownContentHandler
8	1	sun.misc.Unsafe
8	1	sun.reflect.GeneratedConstructorAccessor1
8	1	java.util.Collections$ReverseComparator
8	1	sun.net.www.protocol.file.Handler
8	1	java.util.Hashtable$EmptyEnumerator
8	1	java.util.Hashtable$EmptyIterator
Heap traversal took 1.63 seconds.

こうやって、どのクラスがどれだけの容量の参照を保持しているのか、メモリリークの発見が可能というわけ。

Jmapは確かJava5以上対応で、OSとJDKのバージョンによって挙動と対応状況が異なる。そんでMacではJava5だと何故かメモリ使用の合計値を表示してくれないので合計値を出力してくれる簡単なシェルを記述。

#!/bin/bash
JMAP_CMD=jmap
JPS_CMD=jps
TARGET_PNAME=HelloWorld

PID=`$JPS_CMD |grep $TARGET_PNAME | cut -f 1 -d ' '`
if [ "$PID" = "" ]; then
    echo "There are no processes. => "$TARGET_PNAME
    exit
fi
echo "Target process => "$TARGET_PNAME

$JMAP_CMD -histo $PID |awk '{ if($1 ~ /[0-9]+/){TOTAL+=$1;} print $0; }  \
END { TOTAL/=1024; print "  TOTAL:"TOTAL"[kb]";} '

いちいちjpsコマンド打つのも面倒なので、java実行時のクラス名を指定することでjps-jmap実行までを自動化。(実行時のクラス名はTARGET_PNAMEの値で設定)

出力は以下みたいな感じで、一番最後に合計値が出力される。

  ...
  ...
  TOTAL:26062.6[kb]