4年前 (2020-11-20)  jvm |   抢沙发  2124 
文章评分 0 次,平均分 0.0

调整应用程序的内存使用需要了解Java如何使用内存,以及如何获得应用程序内存使用的可见性。

JVM内存使用率

JVM以多种不同的方式使用内存。内存的主要使用(但不是单一的)在堆中。堆之外,元空间和堆栈也会消耗内存。

Java堆——堆是存储类实例化(或对象)的地方。实例变量存储在对象中。在讨论Java内存和优化时,我们最常讨论堆,因为我们对堆的控制最大,而堆也是垃圾收集(和GC优化)发生的地方。堆大小由-Xms-Xmx JVM标志控制。阅读有关GC和堆的更多信息

Java堆栈-每个线程都有自己的调用堆栈。堆栈存储原始局部变量和对象引用,以及调用堆栈(方法调用)本身。当堆栈帧移出上下文时,堆栈将被清理,因此这里不执行GC。Xss JVM选项控制为每个线程的堆栈分配多少内存。

元空间-元空间存储对象的类定义。Metaspace的大小由设置-XX:MetaspaceSize控制,设置后出现内存不足会进行metaspace G1垃圾回收。

额外的JVM开销——除了上面提到的,一些内存是由JVM本身消耗的。这包含了JVM的C库和运行上面剩余内存池所需的一些C内存分配开销。运行在JVM上的可见性工具不会显示这种开销,因此虽然它们可以提供应用程序如何使用内存的概念,但不能显示JVM进程的总内存使用情况。这种类型的内存可能会受到调整glibc内存行为的影响。

元空间垃圾回收条件

配置Java以在容器中运行

JVM尝试根据操作系统报告的可用性为其各种内存类别设置默认分配值。但是,当在容器(例如Docker容器)内运行时,操作系统报告的值可能不正确。您可以通过配置JVM使用cgroup内存限制来解决这个问题。

在Java 8上,cgroup内存限制的使用是一个实验性的特性,可以通过向JVM进程中添加以下选项(在Procfile中或使用config变量)来启用:

-XX:+UnlockExperimentalVMOptions -XX:+UseCGroupMemoryLimitForHeap

在Java 9及更高版本上,该选项不再是试验性的:

-XX:+UseContainerSupport

这些选项可能会减少JVM进程的总体内存占用。如果没有,那么在进行任何其他调整之前,您需要研究JVM是如何使用内存的。

分析Java应用程序的内存使用情况

了解应用程序在开发和生产环境中如何使用内存是很重要的。大多数的记忆问题可以在任何环境中重现,而不需要付出很大的努力。解决本地计算机上的内存问题通常更容易,因为您可以访问更多的工具,并且不必担心监视工具可能导致的副作用。

有许多工具可用于深入了解Java应用程序内存使用情况。有些是与Java运行时本身打包的,应该已经在您的开发机器上了。有些可以从第三方获得。这不是一个详尽的列表,而是您探索这些工具的起点。

Java运行时附带的工具包括用于执行堆转储和收集内存统计信息的jmap、用于检查任何给定时间运行的线程的jstack、用于一般JVM统计信息收集的jstat和用于分析堆转储的jhat

VisualVM将上述所有工具组合到一个基于GUI的包中,对某些用户来说更友好。

配置NativeMemoryTracking

如果应用程序的本机内存使用率很高(即,总RSS和JVM堆之间的差异),则可能需要配置应用程序以在关闭时打印本机内存跟踪信息。为此,请设置以下配置变量:

set JAVA_OPTS="-XX:NativeMemoryTracking=detail -XX:+UnlockDiagnosticVMOptions -XX:+PrintNMTStatistics"

使用此配置,您还可以通过运行以下命令启动一次性dyno来调试隔离环境中的内存使用情况:

run bash

然后通过在Procfile命令的末尾添加一个&在后台运行应用程序进程。例如:

$ java -jar myapp.jar &

无论哪种情况,都可以通过运行以下命令来捕获Java进程的进程ID(PID):

$ jps
4 Main
105 Jps

本例中的PID为4。现在您可以使用jstackjmapjcmd等工具来处理这个过程。例如:

$ jcmd 4 VM.native_memory summary
4:

Native Memory Tracking:

Total: reserved=1811283KB, committed=543735KB
-                 Java Heap (reserved=393216KB, committed=390656KB)
                            (mmap: reserved=393216KB, committed=390656KB)

-                     Class (reserved=1095741KB, committed=54165KB)
                            (classes #8590)
                            (malloc=10301KB #14097)
                            (mmap: reserved=1085440KB, committed=43864KB)

-                    Thread (reserved=22290KB, committed=22290KB)
                            (thread #30)
                            (stack: reserved=22132KB, committed=22132KB)
                            (malloc=92KB #155)
                            (arena=66KB #58)
...

从应用程序生成线程转储

也可以从应用程序代码生成线程转储。当进程退出时,这在尝试生成转储时非常有用。例如,您可以向Java应用程序添加以下代码:

Runtime.getRuntime().addShutdownHook(new Thread() {
  @Override
  public void run() {
    final java.lang.management.ThreadMXBean threadMXBean = java.lang.management.ManagementFactory.getThreadMXBean();
    final java.lang.management.ThreadInfo[] threadInfos = threadMXBean.getThreadInfo(threadMXBean.getAllThreadIds(), 100);
    for (java.lang.management.ThreadInfo threadInfo : threadInfos) {
      System.out.println(threadInfo.getThreadName());
      final Thread.State state = threadInfo.getThreadState();
      System.out.println("   java.lang.Thread.State: " + state);
      final StackTraceElement[] stackTraceElements = threadInfo.getStackTrace();
      for (final StackTraceElement stackTraceElement : stackTraceElements) {
        System.out.println("        at " + stackTraceElement);
      }
      System.out.println("\n");
    }
  }
});

或者在Scala Play应用程序中,你可以添加一个应用app/Global.scala包含以下内容的文件:

object Global extends WithFilters() {
  override def onStop(app: Application) {
    var threadMXBean = java.lang.management.ManagementFactory.getThreadMXBean();
    var threadInfos = threadMXBean.getThreadInfo(threadMXBean.getAllThreadIds, 100);
    threadInfos.foreach { threadInfo =>
      if (threadInfo != null) {
        println(s"""
'${threadInfo.getThreadName}': ${threadInfo.getThreadState}
  at ${threadInfo.getStackTrace.mkString("\n  at ")}
""")
      }
    }
  }
}

当进程关闭时,这两个示例都会将线程信息打印到stdout。这样,如果进程陷入僵局,您可以使用如下命令重新启动它:

$ ps:restart web.1

堆栈信息将出现在日志中。

详细GC标志

如果上面的信息不够详细,还可以使用一些JVM选项在GC时在日志中获取详细的输出。将以下标志添加到Java选项:-XX:+PrintGCDetails-XX:+printheapatc-XX:+PrintGCDateStamps

$ config:set JAVA_OPTS='-Xss512k -XX:+UseCompressedOops -XX:+PrintGCDetails -XX:+PrintHeapAtGC -XX:+PrintGCDateStamps'
2012-07-07T04:27:59+00:00 app[web.2]: {Heap before GC invocations=43 (full 0):
2012-07-07T04:27:59+00:00 app[web.2]:  PSYoungGen      total 192768K, used 190896K [0x00000000f4000000, 0x0000000100000000, 0x0000000100000000)
2012-07-07T04:27:59+00:00 app[web.2]:   eden space 188800K, 100% used [0x00000000f4000000,0x00000000ff860000,0x00000000ff860000)
2012-07-07T04:27:59+00:00 app[web.2]:   from space 3968K, 52% used [0x00000000ffc20000,0x00000000ffe2c1e0,0x0000000100000000)
2012-07-07T04:27:59+00:00 app[web.2]:   to   space 3840K, 0% used [0x00000000ff860000,0x00000000ff860000,0x00000000ffc20000)
2012-07-07T04:27:59+00:00 app[web.2]:  ParOldGen       total 196608K, used 13900K [0x00000000e8000000, 0x00000000f4000000, 0x00000000f4000000)
2012-07-07T04:27:59+00:00 app[web.2]:   object space 196608K, 7% used [0x00000000e8000000,0x00000000e8d93070,0x00000000f4000000)
2012-07-07T04:27:59+00:00 app[web.2]:  PSPermGen       total 50816K, used 50735K [0x00000000dda00000, 0x00000000e0ba0000, 0x00000000e8000000)
2012-07-07T04:27:59+00:00 app[web.2]:   object space 50816K, 99% used [0x00000000dda00000,0x00000000e0b8bee0,0x00000000e0ba0000)
2012-07-07T04:27:59+00:00 app[web.2]: 2012-07-07T04:27:59.361+0000: [GC
2012-07-07T04:27:59+00:00 app[web.2]: Desired survivor size 3866624 bytes, new threshold 1 (max 15)
2012-07-07T04:27:59+00:00 app[web.2]:  [PSYoungGen: 190896K->2336K(192640K)] 204796K->16417K(389248K), 0.0058230 secs] [Times: user=0.01 sys=0.00, real=0.01 secs]
2012-07-07T04:27:59+00:00 app[web.2]: Heap after GC invocations=43 (full 0):
2012-07-07T04:27:59+00:00 app[web.2]:  PSYoungGen      total 192640K, used 2336K [0x00000000f4000000, 0x0000000100000000, 0x0000000100000000)
2012-07-07T04:27:59+00:00 app[web.2]:   eden space 188800K, 0% used [0x00000000f4000000,0x00000000f4000000,0x00000000ff860000)
2012-07-07T04:27:59+00:00 app[web.2]:   from space 3840K, 60% used [0x00000000ff860000,0x00000000ffaa82d0,0x00000000ffc20000)
2012-07-07T04:27:59+00:00 app[web.2]:   to   space 3776K, 0% used [0x00000000ffc50000,0x00000000ffc50000,0x0000000100000000)
2012-07-07T04:27:59+00:00 app[web.2]:  ParOldGen       total 196608K, used 14080K [0x00000000e8000000, 0x00000000f4000000, 0x00000000f4000000)
2012-07-07T04:27:59+00:00 app[web.2]:   object space 196608K, 7% used [0x00000000e8000000,0x00000000e8dc0330,0x00000000f4000000)
2012-07-07T04:27:59+00:00 app[web.2]:  PSPermGen       total 50816K, used 50735K [0x00000000dda00000, 0x00000000e0ba0000, 0x00000000e8000000)
2012-07-07T04:27:59+00:00 app[web.2]:   object space 50816K, 99% used [0x00000000dda00000,0x00000000e0b8bee0,0x00000000e0ba0000)
2012-07-07T04:27:59+00:00 app[web.2]: }
 

除特别注明外,本站所有文章均为老K的Java博客原创,转载请注明出处来自https://javakk.com/852.html

关于

发表评论

表情 格式

暂无评论

登录

忘记密码 ?

切换登录

注册