检查Java Metaspace区域的内容
JVM内存有以下区域:
a.年轻一代
b.老一代
c.元空间
d.其他区域
要想知道哪些对象被存储在什么区域,你可以参考这个视频片段。有时你的应用程序可能会遇到 'java.lang.OutOfMemoryError:Metaspace',正如这篇文章中所讨论的。在这种情况下,你可能想看看在JVM的Metaspace区域加载的内容是什么。简而言之,JVM内存中的Metaspace区域包含执行应用程序所需的类元数据定义。如果你想了解类元数据定义的含义,你可以参考这个文档。它有密集的细节,你可能不必了解它的所有细节。基本上,如果你能理解哪些是被加载到内存中的类,它将给出一个很好的想法,哪些是存在于JVM内存的Metaspace区域的内容。在这篇文章中,让我们来探讨一下有哪些选项可以用来查看加载到Metaspace中的类。
以下是查看加载在Metaspace中的类的选项:
1.-verbose:class
2.-Xlog:class+load
3. jcmd GC.class_histogram
4.程序化的方法
5.堆转储分析
让我们在这篇文章中详细讨论每个选项。
1.-verbose:class
如果你运行在Java版本8或以下,那么你可以使用这个选项。当你在启动过程中向你的应用程序传递*'-verbose:class'*选项时,它将打印所有被加载到内存中的类。加载的类将被打印在标准错误流中(即控制台,如果你没有将你的错误流路由到日志文件):
java {app_name} -verbose:class
下面是开放源代码的BuggyApp程序在通过'-verbose:class'参数时的输出示例:
[Opened C:\Program Files\Java\jre1.8.0_171\lib\rt.jar]
[Loaded java.lang.Object from C:\Program Files\Java\jre1.8.0_171\lib\rt.jar]
[Loaded java.io.Serializable from C:\Program Files\Java\jre1.8.0_171\lib\rt.jar]
[Loaded java.lang.Comparable from C:\Program Files\Java\jre1.8.0_171\lib\rt.jar]
[Loaded java.lang.CharSequence from C:\Program Files\Java\jre1.8.0_171\lib\rt.jar]
[Loaded java.lang.String from C:\Program Files\Java\jre1.8.0_171\lib\rt.jar]
[Loaded java.lang.reflect.AnnotatedElement from C:\Program Files\Java\jre1.8.0_171\lib\rt.jar]
[Loaded java.lang.reflect.GenericDeclaration from C:\Program Files\Java\jre1.8.0_171\lib\rt.jar]
[Loaded java.lang.reflect.Type from C:\Program Files\Java\jre1.8.0_171\lib\rt.jar]
[Loaded java.lang.Class from C:\Program Files\Java\jre1.8.0_171\lib\rt.jar]
[Loaded java.lang.Cloneable from C:\Program Files\Java\jre1.8.0_171\lib\rt.jar]
[Loaded java.lang.ClassLoader from C:\Program Files\Java\jre1.8.0_171\lib\rt.jar]
[Loaded java.lang.System from C:\Program Files\Java\jre1.8.0_171\lib\rt.jar]
[Loaded java.lang.Throwable from C:\Program Files\Java\jre1.8.0_171\lib\rt.jar]
[Loaded java.lang.Error from C:\Program Files\Java\jre1.8.0_171\lib\rt.jar]
[Loaded java.lang.ThreadDeath from C:\Program Files\Java\jre1.8.0_171\lib\rt.jar]
[Loaded java.lang.Exception from C:\Program Files\Java\jre1.8.0_171\lib\rt.jar]
[Loaded java.lang.RuntimeException from C:\Program Files\Java\jre1.8.0_171\lib\rt.jar]
[Loaded java.lang.SecurityManager from C:\Program Files\Java\jre1.8.0_171\lib\rt.jar]
[Loaded java.security.ProtectionDomain from C:\Program Files\Java\jre1.8.0_171\lib\rt.jar]
[Loaded java.security.AccessControlContext from C:\Program Files\Java\jre1.8.0_171\lib\rt.jar]
[Loaded java.security.SecureClassLoader from C:\Program Files\Java\jre1.8.0_171\lib\rt.jar]
[Loaded java.lang.ReflectiveOperationException from C:\Program Files\Java\jre1.8.0_171\lib\rt.jar]
[Loaded java.lang.ClassNotFoundException from C:\Program Files\Java\jre1.8.0_171\lib\rt.jar]
[Loaded java.lang.LinkageError from C:\Program Files\Java\jre1.8.0_171\lib\rt.jar]
[Loaded java.lang.NoClassDefFoundError from C:\Program Files\Java\jre1.8.0_171\lib\rt.jar]
[Loaded java.lang.ClassCastException from C:\Program Files\Java\jre1.8.0_171\lib\rt.jar]
[Loaded java.lang.ArrayStoreException from C:\Program Files\Java\jre1.8.0_171\lib\rt.jar]
[Loaded java.lang.VirtualMachineError from C:\Program Files\Java\jre1.8.0_171\lib\rt.jar]
[Loaded java.lang.OutOfMemoryError from C:\Program Files\Java\jre1.8.0_171\lib\rt.jar]
[Loaded java.lang.StackOverflowError from C:\Program Files\Java\jre1.8.0_171\lib\rt.jar]
2.-Xlog:class+load
如果您运行在Java版本9或以上,那么您可以使用这个选项。当你在启动过程中向你的应用程序传递*'-Xlog:class+load'*选项时,它将打印所有被加载到内存中的类。加载的类将被打印在你所配置的文件路径中:
java {app_name} -Xlog:class+load=info:/opt/log/loadedClasses.txt
下面是一个java程序在通过'-Xlog:class+load'参数后的输出示例:
[0.004s][info][class,load] opened: /home/ec2-user/jdk-9.0.4/lib/modules
[0.006s][info][class,load] java.lang.Object source: jrt:/java.base
[0.007s][info][class,load] java.io.Serializable source: jrt:/java.base
[0.007s][info][class,load] java.lang.Comparable source: jrt:/java.base
[0.007s][info][class,load] java.lang.CharSequence source: jrt:/java.base
[0.007s][info][class,load] java.lang.String source: jrt:/java.base
[0.007s][info][class,load] java.lang.reflect.AnnotatedElement source: jrt:/java.base
[0.007s][info][class,load] java.lang.reflect.GenericDeclaration source: jrt:/java.base
[0.007s][info][class,load] java.lang.reflect.Type source: jrt:/java.base
[0.008s][info][class,load] java.lang.Class source: jrt:/java.base
[0.008s][info][class,load] java.lang.Cloneable source: jrt:/java.base
[0.008s][info][class,load] java.lang.ClassLoader source: jrt:/java.base
[0.008s][info][class,load] java.lang.System source: jrt:/java.base
[0.008s][info][class,load] java.lang.Throwable source: jrt:/java.base
[0.008s][info][class,load] java.lang.Error source: jrt:/java.base
[0.008s][info][class,load] java.lang.ThreadDeath source: jrt:/java.base
[0.008s][info][class,load] java.lang.Exception source: jrt:/java.base
[0.008s][info][class,load] java.lang.RuntimeException source: jrt:/java.base
[0.008s][info][class,load] java.lang.SecurityManager source: jrt:/java.base
[0.008s][info][class,load] java.security.ProtectionDomain source: jrt:/java.base
[0.009s][info][class,load] java.security.AccessControlContext source: jrt:/java.base
[0.009s][info][class,load] java.security.SecureClassLoader source: jrt:/java.base
[0.009s][info][class,load] java.lang.ReflectiveOperationException source: jrt:/java.base
[0.009s][info][class,load] java.lang.ClassNotFoundException source: jrt:/java.base
[0.009s][info][class,load] java.lang.LinkageError source: jrt:/java.base
[0.009s][info][class,load] java.lang.NoClassDefFoundError source: jrt:/java.base
[0.009s][info][class,load] java.lang.ClassCastException source: jrt:/java.base
[0.009s][info][class,load] java.lang.ArrayStoreException source: jrt:/java.base
[0.009s][info][class,load] java.lang.VirtualMachineError source: jrt:/java.base
[0.009s][info][class,load] java.lang.OutOfMemoryError source: jrt:/java.base
[0.009s][info][class,load] java.lang.StackOverflowError source: jrt:/java.base
[0.009s][info][class,load] java.lang.IllegalMonitorStateException source: jrt:/java.base
[0.009s][info][class,load] java.lang.ref.Reference source: jrt:/java.base
[0.009s][info][class,load] java.lang.ref.SoftReference source: jrt:/java.base
[0.009s][info][class,load] java.lang.ref.WeakReference source: jrt:/java.base
[0.009s][info][class,load] java.lang.ref.FinalReference source: jrt:/java.base
[0.009s][info][class,load] java.lang.ref.PhantomReference source: jrt:/java.base
[0.009s][info][class,load] java.lang.ref.Finalizer source: jrt:/java.base
[0.009s][info][class,load] java.lang.Runnable source: jrt:/java.base
[0.009s][info][class,load] java.lang.Thread source: jrt:/java.base
[0.009s][info][class,load] java.lang.Thread$UncaughtExceptionHandler source: jrt:/java.base
[0.009s][info][class,load] java.lang.ThreadGroup source: jrt:/java.base
[0.010s][info][class,load] java.util.Map source: jrt:/java.base
[0.010s][info][class,load] java.util.Dictionary source: jrt:/java.base
[0.010s][info][class,load] java.util.Hashtable source: jrt:/java.base
[0.010s][info][class,load] java.util.Properties source: jrt:/java.base
[0.010s][info][class,load] java.lang.Module source: jrt:/java.base
[0.010s][info][class,load] java.lang.reflect.AccessibleObject source: jrt:/java.base
3. jcmd GC.class_histogram
JDK包含一个名为'jcmd'的工具。你可以在JVM运行时调用这个工具来检查Metaspace区域的内容。当你用*'GC.class_histogram'*参数调用这个工具时,它将打印出加载到内存中的类列表。 你可以在两种模式下调用这个工具。
a.在控制台打印加载的类
jcmd {pid} GC.class_histogram
当你如上所示调用'jcmd'时,它将在控制台中打印所有加载的类。这里{pid}是你的java应用程序的进程ID。
b.在文件中打印已加载的类
jcmd {pid} GC.class_histogram filename={file-path}
当你调用如上所示的'jcmd'时,它将在'文件名'参数中指定的文件路径中打印所有加载的类。这里{pid}是你的java应用程序的进程ID:
Here is a blog post which helps you to identify the process id quickly.
下面是开放源代码的BuggyApp程序在通过'jcmd GC.class_histogram'参数时的输出示例:
jcmd 19684 GC.class_histogram
19684:
num #instances #bytes class name
----------------------------------------------
1: 143036 75523008 [Ljavassist.bytecode.ConstInfo;
2: 718060 70032224 [C
3: 1573553 50353696 java.util.HashMap$Node
4: 430124 24732832 [Ljava.lang.Object;
5: 1001290 24030960 javassist.bytecode.Utf8Info
6: 858268 20598432 java.util.ArrayList
7: 718037 17232888 java.lang.String
8: 144011 14987488 java.lang.Class
9: 143081 11447152 [Ljava.util.HashMap$Node;
10: 143036 9154304 javassist.bytecode.ClassFile
11: 143035 9154240 javassist.CtNewClass
12: 286124 6892400 [B
13: 143085 6868080 java.util.HashMap
14: 286078 6865872 javassist.bytecode.ClassInfo
15: 143036 6865728 [[Ljavassist.bytecode.ConstInfo;
16: 143049 5721960 javassist.bytecode.MethodInfo
17: 143042 5721680 javassist.bytecode.CodeAttribute
18: 143323 4586336 java.util.Hashtable$Entry
19: 143038 4577216 java.lang.ref.WeakReference
20: 143036 4577152 javassist.bytecode.ConstPool
21: 143045 3433080 javassist.bytecode.MethodrefInfo
22: 143045 3433080 javassist.bytecode.NameAndTypeInfo
23: 143042 3433008 javassist.bytecode.ExceptionTable
24: 143036 3432864 javassist.bytecode.LongVector
25: 143036 3432864 javassist.bytecode.SourceFileAttribute
26: 143622 2323336 [I
27: 10 788688 [Ljava.util.Hashtable$Entry;
28: 642 20544 java.util.concurrent.ConcurrentHashMap$Node
29: 244 13664 java.lang.invoke.MemberName
30: 341 10912 sun.misc.FDBigInteger
31: 212 8480 java.lang.ref.SoftReference
32: 140 8400 [Ljava.lang.ref.SoftReference;
33: 234 7488 java.lang.invoke.LambdaForm$Name
34: 176 7040 java.lang.invoke.MethodType
35: 256 6144 java.lang.Long
36: 16 6016 java.lang.Thread
37: 173 5880 [Ljava.lang.Class;
38: 366 5856 java.lang.Object
39: 177 5664 java.lang.invoke.MethodType$ConcurrentWeakInternSet$WeakEntry
40: 10 5280 [Ljava.util.concurrent.ConcurrentHashMap$Node;
41: 256 4096 java.lang.Byte
42: 256 4096 java.lang.Integer
43: 256 4096 java.lang.Short
44: 73 4088 java.lang.invoke.MethodTypeForm
45: 82 3808 [Ljava.lang.invoke.LambdaForm$Name;
46: 77 3696 java.lang.invoke.LambdaForm
4.程序化的方法
你也可以使用程序化的方法来打印加载到内存中的类。开源的Guava库提供了打印加载类的API。下面是利用Guava库打印加载到内存中的类的代码示例:
ClassPath classPath = ClassPath.from(BuggyAppLoader.class.getClassLoader());
Set<ClassInfo> classes = classPath.getAllClasses();
for(ClassInfo classInfo : classes) {
logger.info(classInfo.getName());
}
org.apache.catalina.core.AsyncContextImpl
org.apache.catalina.core.AsyncListenerWrapper
org.apache.catalina.core.Constants
org.apache.catalina.core.ContainerBase$ContainerBackgroundProcessor
org.apache.catalina.core.ContainerBase$PrivilegedAddChild
org.apache.catalina.core.ContainerBase$StartChild
org.apache.catalina.core.ContainerBase$StartStopThreadFactory
org.apache.catalina.core.ContainerBase$StopChild
org.apache.catalina.core.ContainerBase
org.apache.catalina.core.DefaultInstanceManager$1
org.apache.catalina.core.DefaultInstanceManager$2
org.apache.catalina.core.DefaultInstanceManager$3
org.apache.catalina.core.DefaultInstanceManager$AnnotationCacheEntry
org.apache.catalina.core.DefaultInstanceManager$AnnotationCacheEntryType
org.apache.catalina.core.DefaultInstanceManager
org.apache.catalina.core.JreMemoryLeakPreventionListener
org.apache.catalina.core.NamingContextListener
org.apache.catalina.core.StandardContext$1
org.apache.catalina.core.StandardContext$ContextFilterMaps
org.apache.catalina.core.StandardContext$NoPluggabilityServletContext
org.apache.catalina.core.StandardContext
org.apache.catalina.core.StandardContextValve
org.apache.catalina.core.StandardEngine$AccessLogListener
org.apache.catalina.core.StandardEngine$NoopAccessLog
org.apache.catalina.core.StandardEngine
org.apache.catalina.core.StandardEngineValve
org.apache.catalina.core.StandardHost$1
org.apache.catalina.core.StandardHost$MemoryLeakTrackingListener
org.apache.catalina.core.StandardHost
org.apache.catalina.core.StandardHostValve
org.apache.catalina.core.StandardPipeline
org.apache.catalina.core.StandardServer
org.apache.catalina.core.StandardService
org.apache.catalina.core.StandardThreadExecutor
org.apache.catalina.core.StandardWrapper
org.apache.catalina.core.StandardWrapperFacade
org.apache.catalina.core.StandardWrapperValve
org.apache.catalina.core.ThreadLocalLeakPreventionListener
5.堆转储分析
另一个查看被加载到内存中的类的方法是检查堆转储。堆转储报告了所有加载到内存中的数据、对象和类。你可以使用这里给出的方法之一来捕获堆转储。一旦捕获了堆转储,你就可以使用堆转储分析工具,如Eclipse MAT、HeapHero...来分析堆转储。
下面是HeapHero工具生成的报告的摘录,显示了加载到内存中的类:
Note: All the approaches mentioned above will not add noticeable overhead to your application, however the heap dump approach is an intrusive option and it will add considerable overhead to your application. When heap dump is captured your application will be paused until capturing is complete.

