如何检查Java Metaspace区域的内容(附代码)

1,409 阅读5分钟

检查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 MATHeapHero...来分析堆转储。

下面是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.