建设淘宝网站的市场分析,南京手机网站设计公司,php 新闻类网站怎么做,mvc网站建设的实验报告目录
Exit Code 137
解决方案
JVM 感知 cgroup 限制
使用 JDK9 的容器感知机制尝试
问题分析
容器内部感知 CGroup 资源限制
在 Java10 中#xff0c;改进了容器集成
JVM 参数 MaxDirectMemorySize
-XX:MaxDirectMemorySize 的默认值是什么#xff1f;
其他获取 ma…目录
Exit Code 137
解决方案
JVM 感知 cgroup 限制
使用 JDK9 的容器感知机制尝试
问题分析
容器内部感知 CGroup 资源限制
在 Java10 中改进了容器集成
JVM 参数 MaxDirectMemorySize
-XX:MaxDirectMemorySize 的默认值是什么
其他获取 maxDirectMemory 的值的 API 方法
内存分析问题
-XX:DisableExplicitGC 与 NIO 的 direct memory 我们日常的工作当中通常应用都会采用 Kubernetes 进行容器化部署但是总是会出现一些问题例如JVM 堆小于 Docker 容器中设置的内存大小和 Kubernetes 的内存大小但是还是会被 OOMKilled。在此我们介绍一下 K8s 的 OOMKilled 的 Exit Code 编码。
Exit Code 137 表明容器收到了 SIGKILL 信号进程被杀掉对应 kill -9引发 SIGKILL 的是 docker kill。这可以由用户或由 docker 守护程序来发起手动执行docker kill 137 比较常见如果 pod 中的 limit 资源设置较小会运行内存不足导致 OOMKilled此时 state 中的 ”OOMKilled” 值为 true你可以在系统的 dmesg -T 中看到 OOM 日志。
因为我的 heap 大小肯定是小于 Docker 容器以及 Pod 的大小的为啥还是会出现 OOMKilled
这种问题常发生在 JDK8u131 或者 JDK9 版本之后所出现在容器中运行 JVM 的问题在大多数情况下JVM 将一般默认会采用宿主机 Node 节点的内存为 Native VM 空间其中包含了堆空间、直接内存空间以及栈空间而并非是是容器的空间为标准。
例如我的机器
$ docker run -m 100MB openjdk:8u121 java -XshowSettings:vm -version
VM settings:Max. Heap Size (Estimated): 444.50MErgonomics Machine Class: serverUsing VM: OpenJDK 64-Bit Server VM以上的信息出现了矛盾我们在运行的时候将容器内存设置为 100MB而 -XshowSettings:vm 打印出的 JVM 将最大堆大小为 444M如果按照这个内存进行分配内存的话很可能会导致节点主机在某个时候杀死我的 JVM。
解决方案
JVM 感知 cgroup 限制
一种方法解决 JVM 内存超限的问题这种方法可以让 JVM 自动感知 docker 容器的 cgroup 限制从而动态的调整堆内存大小。JDK8u131 在 JDK9 中有一个很好的特性即 JVM 能够检测在 Docker 容器中运行时有多少内存可用。为了使 jvm 保留根据容器规范的内存必须设置标志 -XX:UnlockExperimentalVMOptions -XX:UseCGroupMemoryLimitForHeap。
❝ 注意如果将这两个标志与 Xms 和 Xmx 标志一起设置那么 jvm 的行为将是什么-Xmx 标志将覆盖-XX: UseCGroupMemoryLimitForHeap 标志。 总结一下 标志 -XX:UseCGroupMemoryLimitForHeap 使 JVM 可以检测容器中的最大堆大小。 -Xmx 标志将最大堆大小设置为固定大小。 除了 JVM 的堆空间还会对于非堆和 jvm 的东西还会有一些额外的内存使用情况。
使用 JDK9 的容器感知机制尝试
$ docker run -m 100MB openjdk:8u131 java \-XX:UnlockExperimentalVMOptions \-XX:UseCGroupMemoryLimitForHeap \-XshowSettings:vm -version
VM settings:Max. Heap Size (Estimated): 44.50MErgonomics Machine Class: serverUsing VM: OpenJDK 64-Bit Server VM
可以看出来通过内存感知之后JVM 能够检测到容器只有 100MB并将最大堆设置为 44M。我们调整一下内存大小看看是否可以实现动态化调整和感知内存分配如下所示。
$ docker run -m 1GB openjdk:8u131 java \-XX:UnlockExperimentalVMOptions \-XX:UseCGroupMemoryLimitForHeap \-XshowSettings:vm -version
VM settings:Max. Heap Size (Estimated): 228.00MErgonomics Machine Class: serverUsing VM: OpenJDK 64-Bit Server VM我们设置了容器有 1GB 内存分配而 JVM 使用 228M 作为最大堆。因为容器中除了 JVM 之外没有其他进程在运行所以我们还可以进一步扩大一下对于 Heap 堆的分配
$ docker run -m 1GB openjdk:8u131 java \-XX:UnlockExperimentalVMOptions \-XX:UseCGroupMemoryLimitForHeap \-XX:MaxRAMFraction1 -XshowSettings:vm -version
VM settings:Max. Heap Size (Estimated): 910.50MErgonomics Machine Class: serverUsing VM: OpenJDK 64-Bit Server VM在较低的版本的时候可以使用 -XX:MaxRAMFraction 参数它告诉 JVM 使用可用内存 /MaxRAMFract 作为最大堆。使用 -XX:MaxRAMFraction1我们将几乎所有可用内存用作最大堆。从上面的结果可以看出来内存分配已经可以达到了 910.50M。
问题分析 最大堆占用总内存是否仍然会导致你的进程因为内存的其他部分如“元空间”而被杀死
答案MaxRAMFraction1 仍将为其他非堆内存留出一些空间。
❝ 但如果容器使用堆外内存这可能会有风险因为几乎所有的容器内存都分配给了堆。您必须将-XX:MaxRAMFraction2 设置为堆只使用 50% 的容器内存或者使用 Xmx。 容器内部感知 CGroup 资源限制
Docker1.7 开始将容器 cgroup 信息挂载到容器中所以应用可以从 /sys/fs/cgroup/memory/memory.limit_in_bytes 等文件获取内存、 CPU 等设置在容器的应用启动命令中根据 Cgroup 配置正确的资源设置 -Xmx, -XX:ParallelGCThreads 等参数
在 Java10 中改进了容器集成 Java10 废除了 -XX:MaxRAM 参数因为 JVM 将正确检测该值。在 Java10 中改进了容器集成。无需添加额外的标志JVM 将使用 1/4 的容器内存用于堆。 java10 确实正确地识别了内存的 Docker 限制但您可以使用新的标志 MaxRAMPercentage例如-XX:MaxRAMPercentage75而不是旧的 MaxRAMFraction以便更精确地调整堆的大小而不是其余的堆栈、本机… java10 上的 UseContainerSupport 选项而且是默认启用的不用设置。同时 UseCGroupMemoryLimitForHeap 这个就弃用了不建议继续使用同时还可以通过 -XX:InitialRAMPercentage、-XX:MaxRAMPercentage、-XX:MinRAMPercentage 这些参数更加细腻的控制 JVM 使用的内存比率。
❝ Java 程序在运行时会调用外部进程、申请 Native Memory 等所以即使是在容器中运行 Java 程序也得预留一些内存给系统的。所以 -XX:MaxRAMPercentage 不能配置得太大。当然仍然可以使用 -XX:MaxRAMFraction1 选项来压缩容器中的所有内存。 通过前面的讲解我们知道了如何设置和控制 Java 应用对应的堆内存和容器内存的之间的关系进而防止 JVM 的堆内存超过了容器内存避免容器出现 OOMKilled 的情况。但是在整个 JVM 进程体系而言不仅仅只包含了 Heap 堆内存其实还有其他相关的内存存储空间是需要我们考虑的一边防止这些内存空间会造成我们的容器内存溢出的场景正如下图所示。 接下来我们需要进行分析出 heap 之外的一部分就是对外内存就是 Off Heap Space也就是 Direct buffer memory 堆外内存。主要通过的方式就是采用 Unsafe 方式进行申请内存大多数场景也会通过 Direct ByteBuffer 方式进行获取。好废话不多说进入正题。
JVM 参数 MaxDirectMemorySize
我们先研究一下 jvm 的 -XX:MaxDirectMemorySize该参数指定了 DirectByteBuffer 能分配的空间的限额如果没有显示指定这个参数启动 jvm默认值是 xmx 对应的值低版本是减去幸存区的大小。
DirectByteBuffer 对象是一种典型的”冰山对象”在堆中存在少量的泄露的对象但其下面连接用堆外内存这种情况容易造成内存的大量使用而得不到释放
-XX:MaxDirectMemorySize-XX:MaxDirectMemorySizesize 用于设置 New I/O (java.nio) direct-buffer allocations 的最大大小size 的单位可以使用 k/K、m/M、g/G如果没有设置该参数则默认值为 0意味着 JVM 自己自动给 NIO direct-buffer allocations 选择最大大小。
-XX:MaxDirectMemorySize 的默认值是什么
在 sun.misc.VM 中它是 Runtime.getRuntime.maxMemory()这就是使用-Xmx 配置的内容。而对应的 JVM 参数如何传递给 JVM 底层的呢主要通过的是 hotspot/share/prims/jvm.cpp。我们来看一下 jvm.cpp 的 JVM 源码来分一下。 // Convert the -XX:MaxDirectMemorySize command line flag// to the sun.nio.MaxDirectMemorySize property.// Do this after setting user properties to prevent people// from setting the value with a -D option, as requested.// Leave empty if not suppliedif (!FLAG_IS_DEFAULT(MaxDirectMemorySize)) {char as_chars[256];jio_snprintf(as_chars, sizeof(as_chars), JULONG_FORMAT, MaxDirectMemorySize);Handle key_str java_lang_String::create_from_platform_dependent_str(sun.nio.MaxDirectMemorySize, CHECK_NULL);Handle value_str java_lang_String::create_from_platform_dependent_str(as_chars, CHECK_NULL);result_h-obj_at_put(ndx * 2, key_str());result_h-obj_at_put(ndx * 2 1, value_str());ndx;}jvm.cpp 里头有一段代码用于把 -XX:MaxDirectMemorySize 命令参数转换为 key 为 sun.nio.MaxDirectMemorySize 的属性。我们可以看出来他转换为了该属性之后进行设置和初始化直接内存的配置。针对于直接内存的核心类就在www.docjar.com/html/api/su…[1]
public class VM {// the init level when the VM is fully initializedprivate static final int JAVA_LANG_SYSTEM_INITED 1;private static final int MODULE_SYSTEM_INITED 2;private static final int SYSTEM_LOADER_INITIALIZING 3;private static final int SYSTEM_BOOTED 4;private static final int SYSTEM_SHUTDOWN 5;// 0, 1, 2, ...private static volatile int initLevel;private static final Object lock new Object();//......// A user-settable upper limit on the maximum amount of allocatable direct// buffer memory. This value may be changed during VM initialization if// java is launched with -XX:MaxDirectMemorySizesize.//// The initial value of this field is arbitrary; during JRE initialization// it will be reset to the value specified on the command line, if any,// otherwise to Runtime.getRuntime().maxMemory().//private static long directMemory 64 * 1024 * 1024;上面可以看出来 64MB 最初是任意设置的。在 -XX:MaxDirectMemorySize 是用来配置 NIO direct memory 上限用的 VM 参数。可以看一下 JVM 的这行代码。
product(intx, MaxDirectMemorySize, -1,Maximum total size of NIO direct-buffer allocations)但如果不配置它的话direct memory 默认最多能申请多少内存呢这个参数默认值是-1显然不是一个“有效值”。所以真正的默认值肯定是从别的地方来的。 // Returns the maximum amount of allocatable direct buffer memory.// The directMemory variable is initialized during system initialization// in the saveAndRemoveProperties method.//public static long maxDirectMemory() {return directMemory;}//......// Save a private copy of the system properties and remove// the system properties that are not intended for public access.//// This method can only be invoked during system initialization.public static void saveProperties(MapString, String props) {if (initLevel() ! 0)throw new IllegalStateException(Wrong init level);// only main thread is running at this time, so savedProps and// its content will be correctly published to threads started laterif (savedProps null) {savedProps props;}// Set the maximum amount of direct memory. This value is controlled// by the vm option -XX:MaxDirectMemorySizesize.// The maximum amount of allocatable direct buffer memory (in bytes)// from the system property sun.nio.MaxDirectMemorySize set by the VM.// If not set or set to -1, the max memory will be used// The system property will be removed.String s props.get(sun.nio.MaxDirectMemorySize);if (s null || s.isEmpty() || s.equals(-1)) {// -XX:MaxDirectMemorySize not given, take defaultdirectMemory Runtime.getRuntime().maxMemory();} else {long l Long.parseLong(s);if (l -1)directMemory l;}// Check if direct buffers should be page aligneds props.get(sun.nio.PageAlignDirectMemory);if (true.equals(s))pageAlignDirectMemory true;}//......
}从上面的源码可以读取 sun.nio.MaxDirectMemorySize 属性如果为 null 或者是空或者是 - 1那么则设置为 Runtime.getRuntime().maxMemory()如果有设置 MaxDirectMemorySize 且值大于 -1那么使用该值作为 directMemory 的值而 VM 的 maxDirectMemory 方法则返回的是 directMemory 的值。
因为当 MaxDirectMemorySize 参数没被显式设置时它的值就是 -1在 Java 类库初始化时 maxDirectMemory() 被 java.lang.System 的静态构造器调用走的路径就是这条
if (s.equals(-1)) { // -XX:MaxDirectMemorySize not given, take default directMemory Runtime.getRuntime().maxMemory();
}而 Runtime.maxMemory() 在 HotSpot VM 里的实现是
JVM_ENTRY_NO_ENV(jlong, JVM_MaxMemory(void)) JVMWrapper(JVM_MaxMemory); size_t n Universe::heap()-max_capacity(); return convert_size_t_to_jlong(n);
JVM_END这个 max_capacity() 实际返回的是 -Xmx 减去一个 survivor space 的预留大小。
结论分析说明
MaxDirectMemorySize 没显式配置的时候NIO direct memory 可申请的空间的上限就是 -Xmx 减去一个 survivor space 的预留大小。例如如果您不配置 -XX:MaxDirectMemorySize 并配置 -Xmx5g则 默认 MaxDirectMemorySize 也将是 5GB-survivor space 区并且应用程序的总堆直接内存使用量可能会增长到 5 5 10 Gb。
其他获取 maxDirectMemory 的值的 API 方法
BufferPoolMXBean 及 JavaNioAccess.BufferPool (通过 SharedSecrets 获取) 的 getMemoryUsed 可以获取 direct memory 的大小其中 java9 模块化之后SharedSecrets 从原来的 sun.misc.SharedSecrets 变更到 java.base 模块下的 jdk.internal.access.SharedSecrets要使用 --add-exports java.base/jdk.internal.accessALL-UNNAMED 将其导出到 UNNAMED这样才可以运行
public BufferPoolMXBean getDirectBufferPoolMBean(){return ManagementFactory.getPlatformMXBeans(BufferPoolMXBean.class).stream().filter(e - e.getName().equals(direct)).findFirst().orElseThrow();
}
public JavaNioAccess.BufferPool getNioBufferPool(){return SharedSecrets.getJavaNioAccess().getDirectBufferPool();
}内存分析问题
-XX:DisableExplicitGC 与 NIO 的 direct memory 用了 -XX:DisableExplicitGC 参数后System.gc() 的调用就会变成一个空调用完全不会触发任何 GC但是“函数调用”本身的开销还是存在的哦。 做 ygc 的时候会将新生代里的不可达的 DirectByteBuffer 对象及其堆外内存回收了但是无法对 old 里的 DirectByteBuffer 对象及其堆外内存进行回收这也是我们通常碰到的最大的问题如果有大量的 DirectByteBuffer 对象移到了 old但是又一直没有做 cms gc 或者 full gc而只进行 ygc那么我们的物理内存可能被慢慢耗光但是我们还不知道发生了什么因为 heap 明明剩余的内存还很多 (前提是我们禁用了 System.gc)