JDK1.8之前版本使用K8S对Java应用进行资源限制的注意事项
myluzh 发布于 阅读:809 Kubernetes
0x01 前言
flowchart TD
%% 定义最外层的 Kubernetes 限制
K8s["🔴 Kubernetes 容器 Limit (例如: 2048Mi)
(操作系统 Cgroups 物理内存硬限制)
触碰此线 -> 容器暴毙: OOMKilled (Exit Code 137)"]
%% 第一层拆分:JVM 和 其他系统进程
JVM["🔵 JVM 进程 (Java Process)"]
SysOut["⚙️ 其他容器系统开销
(如 shell 进程, 监控 Agent, Page Cache 等)"]
%% 第二层拆分:堆与堆外 (75% vs 25%)
Heap["🟢 Heap (堆内存)
由 MaxRAMPercentage=75.0 控制 (约 1536Mi)
存储业务对象 (Eden, Survivor, Old Gen)
触碰此线 -> 抛出异常: OutOfMemoryError"]
NonHeap["🟡 Non-Heap (堆外内存 / 本地内存)
剩余的 25% 空间 (约 512Mi)
这是为了防止被 K8s 强杀的『安全缓冲区』"]
%% 第三层拆分:堆外内存的具体组成
Metaspace["1️⃣ Metaspace (元空间)
存放类元数据
建议限制: -XX:MaxMetaspaceSize=256m"]
Threads["2️⃣ 线程栈 (Thread Stacks)
每个线程占用内存
默认 -Xss1m (如 300个线程=300Mi)"]
Direct["3️⃣ 直接内存 (Direct Buffers)
NIO, Netty, 堆外缓存等申请的系统物理内存"]
Code["4️⃣ Code Cache (代码缓存)
JIT 即时编译器生成的机器码"]
Overhead["5️⃣ JVM 自身开销
GC 维护数据 (如 Card Table) 等底层 C++ 开销"]
%% 建立连接关系
K8s --> JVM
K8s --> SysOut
JVM --> Heap
JVM --> NonHeap
NonHeap --> Metaspace
NonHeap --> Threads
NonHeap --> Direct
NonHeap --> Code
NonHeap --> Overhead
%% 样式美化
style K8s fill:#ffebee,stroke:#c62828,stroke-width:3px,color:#b71c1c
style JVM fill:#e3f2fd,stroke:#1565c0,stroke-width:2px
style SysOut fill:#f5f5f5,stroke:#9e9e9e,stroke-width:1px,stroke-dasharray: 5 5
style Heap fill:#e8f5e9,stroke:#2e7d32,stroke-width:2px
style NonHeap fill:#fff3e0,stroke:#ef6c00,stroke-width:2px
style Metaspace fill:#fffde7,stroke:#fbc02d,stroke-width:1px
style Threads fill:#fffde7,stroke:#fbc02d,stroke-width:1px
style Direct fill:#fffde7,stroke:#fbc02d,stroke-width:1px
style Code fill:#fffde7,stroke:#fbc02d,stroke-width:1px
style Overhead fill:#fffde7,stroke:#fbc02d,stroke-width:1px
在处理容器化环境的 OOM 问题时,厘清 Cgroups 资源边界和 JVM 内存划分是最关键的一步。
自上而下的容器内 Java 进程内存结构图。你可以把它想象成一个俄罗斯套娃,最外层是 Kubernetes 设下的“死线”,里面才是 JVM 的各个地盘:
+-------------------------------------------------------------------------+
| Kubernetes Pod / Container Limits |
| (Linux Cgroups 物理内存上限 - 比如设为 2048Mi) |
+=========================================================================+
| | |
| | OS & Container Overhead (系统开销) |
| JVM 进程总内存占用 | (如 sh/bash 进程、监控 Agent、 |
| (Java Process) | Linux Page Cache 页面缓存等) |
| | |
+----------------------------------+--------------------------------------+
| |
| +---------------------------------------------------------------------+ |
| | JVM 内部空间 | |
| +------------------------------------+--------------------------------+ |
| | | | |
| | Heap (堆内存) | Non-Heap (堆外/本地内存) | |
| | (我们设置的 75% 比例) | (剩下的 25% "安全缓冲区") | |
| | | | |
| +------------------------------------+--------------------------------+ |
| | - Young Gen (新生代): | - Metaspace (元空间/类元数据) | |
| | - Eden 区 | - Thread Stacks (线程栈) | |
| | - Survivor 区 (S0/S1) | - Direct Buffers (NIO直接内存) | |
| | | - Code Cache (JIT 编译机器码) | |
| | - Old Gen (老年代) | - JVM Overhead (GC内部数据结构)| |
| +------------------------------------+--------------------------------+ |
+-------------------------------------------------------------------------+
在JDK 1.8之前的版本中,Java本身并不了解Kubernetes中的资源限制,因此如果Java应用程序申请的资源超出了Pod的限制,Kubernetes将直接kill该Pod。
所以需要在Dockerfile中打包镜像时传递环境变量并将其传递给Java命令,可以按照以下步骤进行操作:
第一步:在Dockerfile中设置环境变量
ENV JAVA_OPTS="$JAVA_OPTS -Dfile.encoding=UTF8 -Duser.timezone=GMT+08"
CMD java -jar $JAVA_OPTS xxx.jar
上述代码将环境变量JAVA_OPTS设置为$JAVA_OPTS -Dfile.encoding=UTF8 -Duser.timezone=GMT+08,并将其传递给Java命令。
第二步:在Kubernetes的YAML文件中设置环境变量和资源限制
apiVersion: v1
kind: Pod
metadata:
name: your-pod
spec:
containers:
- name: your-container
image: your-image
env:
- name: JAVA_OPTS
value: "-Xmx1g"
resources:
limits:
memory: 2Gi
cpu: 1
requests:
memory: 1Gi
cpu: 0.5
在上述示例中,我们在YAML文件中定义了一个名为your-pod的Pod,并在其中的容器定义中设置了环境变量JAVA_OPTS的值为-Xmx1g。此外,我们还设置了资源限制,包括内存和CPU的限制。