调整应用程序的内存使用需要了解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。现在您可以使用jstack
、jmap
和jcmd
等工具来处理这个过程。例如:
$ 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
暂无评论