您当前的位置:首页 > 电脑百科 > 程序开发 > 容器

Kubernetes 中的 Java 应用的内存调优

时间:2023-10-18 13:46:15  来源:微信公众号  作者:新钛云服

 

前言

在 Kube.NETes 环境中运行 JAVA 应用程序虽然很常见,但往往也充满各种问题,特别是在管理内存资源时。在本文中,我们将讨论配置应用程序以优化 Kubernetes 环境中的内存使用并避免内存不足问题的一些最佳实践。

OpenJDK 17 中的内存空间

OpenJDK 17 包含 Java 虚拟机 (JVM) 使用的多个内存空间来管理 Java 应用程序的内存。了解这些不同的内存空间可以帮助开发人员针对 Kubernetes 环境优化其 Java 应用程序。

Kubernetes 中的 Java 应用的内存调优

Heap Memory-堆内存

堆内存会在Java运行时分配给对象(Object)或者JRE类。每当我们创建一个对象的时候,在堆内存中就会分配一块储存空间给这个对象。Java的垃圾回收机制就是运行在堆内存上的,用以释放那些没有任何引用指向自身的对象(不可达的对象。注意Java的垃圾回收也会处理几个相互引用但没有任何外部引用的对象)。任何在堆内存中分配的对象都有全局访问权限,可以从应用的任何地方被引用。

堆内存是存储Java应用程序创建的对象的地方。它是Java应用程序最重要的内存空间。在 OpenJDK 17 中,默认堆大小是根据可用物理内存计算的,并设置为可用内存的 1/4。

Young Generation-年轻代

对象被创建时,内存的分配首先发生在年轻代(大对象可以直接被创建在年老代),大部分的对象在创建后很快就不再使用,因此很快变得不可达,于是被年轻代的GC机制清理掉(IBM的研究表明,98%的对象都是很快消亡的),这个GC机制被称为Minor GC或叫Young GC。注意,Minor GC并不代表年轻代内存不足,它事实上只表示在Eden区上的GC。

年轻代上的内存分配是这样的,年轻代可以分为3个区域:Eden区(伊甸园,亚当和夏娃偷吃禁果生娃娃的地方,用来表示内存首次分配的区域,再贴切不过)和两个存活区(Survivor 0 、Survivor 1)。

Old Generation-老一辈

对象如果在年轻代存活了足够长的时间而没有被清理掉(即在几次Young GC后存活了下来),则会被复制到年老代,年老代的空间一般比年轻代大,能存放更多的对象,在年老代上发生的GC次数也比年轻代少。当年老代内存不足时,将执行Major GC,也叫 Full GC。  

可以使用-XX:+UseAdaptiveSizePolicy开关来控制是否采用动态控制策略,如果动态控制,则动态调整Java堆中各个区域的大小以及进入老年代的年龄。

如果对象比较大(比如长字符串或大数组),Young空间不足,则大对象会直接分配到老年代上(大对象可能触发提前GC,应少用,更应避免使用短命的大对象)。用-XX:PretenureSizeThreshold来控制直接升入老年代的对象大小,大于这个值的对象会直接分配在老年代上。

可能存在年老代对象引用新生代对象的情况,如果需要执行Young GC,则可能需要查询整个老年代以确定是否可以清理回收,这显然是低效的。解决的方法是,年老代中维护一个512 byte的块——”card table“,所有老年代对象引用新生代对象的记录都记录在这里。Young GC时,只要查这里即可,不用再去查全部老年代,因此性能大大提高。

Metaspace-元空间

非堆空间被 JVM 用于存储元数据和类定义。在旧版本的 Java 中,它也称为永久代 (PermGen)。在 OpenJDK 17 中,PermGen 空间已被新的 Metaspace 取代,其设计更加高效和灵活。

Code Cache-代码缓存

简而言之,JVM Code Cache (代码缓存)是JVM存储编译成本机代码的字节码的区域。我们将可执行本机代码的每个块称为nmethod。nmethod可能是一个完整的或内联的Java方法。

即时(JIT)编译器是代码缓存区的最大消费者。这就是为什么一些开发人员将此内存称为JIT代码缓存。

Thread Stack Space-线程堆栈空间

Java程序中,每个线程都有自己的Stack Space(堆栈)。这个Stack Space不是来自Heap的分配。所以Stack Space的大小不会受到-Xmx和-Xms的影响,这2个JVM参数仅仅是影响Heap的大小。

Stack Space用来做方法的递归调用时压入Stack Frame(栈帧)。所以当递归调用太深的时候,就有可能耗尽Stack Space,爆出StackOverflow的错误。

-Xss128k:设置每个线程的堆栈大小。JDK5.0以后每个线程堆 栈大小为1M,以前每个线程堆栈大小为256K。根据应用的线程所需内存大小进行调整。在相同物理内存下,减小这个值能生成更多的线程。但是操作系统对一 个进程内的线程数还是有限制的,不能无限生成,经验值在3000~5000左右。

线程栈的大小是个双刃剑,如果设置过小,可能会出现栈溢出,特别是在该线程内有递归、大的循环时出现溢出的可能性更大,如果该值设置过大,就有影响到创建栈的数量,如果是多线程的应用,就会出现内存溢出的错误。

Shared libs-共享库

Java JVM 中的共享库空间(也称为共享类数据空间)是用于存储共享类元数据和其他数据结构的内存空间。该内存空间在多个 Java 进程之间共享。这允许在同一台机器上运行的各种 Java 应用程序共享类元数据和其他数据结构的相同副本。 

共享库空间的目的是通过避免同一类元数据的重复副本来减少内存使用并提高性能。当多个 Java 进程使用相同的类元数据时,它们可以共享该元数据的相同副本,从而减少内存使用并缩短应用程序的启动时间。

为什么要微调JVM的内存设置?

JVM 的默认行为会给 Kubernetes 带来很多麻烦。正如我们之前看到的,堆默认设置为可用内存的 1/4。由于 JVM 将考虑 pod 可用的最大内存(有限制),因此堆的大小可能会比我们想要的大。此外,其他默认值将应用于其他空间,例如代码缓存或元空间。 如果从 JVM 的角度来看最大可用内存,它将大于提供给 pod 的最大可用内存。这将导致应用程序出现许多内存不足的情况(在 Kubernetes 部分)。

避免Java应用程序在Kubernetes上出现OOM

大多数时候,都是为了微调 JVM。由于我们看到 JVM 涉及不同的内存空间,因此我们必须为每个空间设置特定的大小。这将帮助我们更精确地计算 pod 的内存限制。 以下是显示每个内存空间可用选项的架构

Kubernetes 中的 Java 应用的内存调优

基本公式是:

Heap + Metaspace + Code Cache

意思是 :

-XmX + -XX:MaxMetaspaceSize + -XX:ReservedCodeCacheSize

由于线程的数量取决于应用程序的上下文,因此建议为此部分添加一些“缓冲”内存。默认情况下,线程堆栈最大设置为 1MB。

如果想处理来自 JVM 的堆转储,则需要添加堆的大小作为第二次可用的“额外”内存。 最后,设置 pod 限制的公式为:

(-XmX * 2) + -XX:MaxMetaspaceSize + -XX:ReservedCodeCacheSize + SomeBuffer

缓冲区部分取决于上下文,128 MB 应该可以开始。

Helm模板配置

既然有了公式,我们就可以使用一些 Helm 模板自动计算 pod 的请求和限制。为开发人员提供一个简单的选项来设置不同的参数,而无需担心 Pods 设置,这也是一个好的方式。 以下是默认值的示例:

jvm:
 garbageCollector: -XX:+UseG1GC
 # values in Mi
 memory:
   heap: 128
   metaspace: 256
   compressedClassSpaceSize: 64
   nonMethodCodeHeapSize: 5
   profiledCodeHeapSize: 48
   nonProfiledCodeHeapSize: 48
   buffer: 128

使用 Helper 来设置 JAVA_TOOL_OPTIONS :

{{/*
JVM customisation
*/}}
{{- define "chart.javaToolOptions" -}}
-Xms{{.Values.jvm.memory.heap}}m -Xmx{{.Values.jvm.memory.heap}}m -XX:MetaspaceSize={{.Values.jvm.memory.metaspace}}m -XX:MaxMetaspaceSize={{.Values.jvm.memory.metaspace}}m -XX:CompressedClassSpaceSize={{.Values.jvm.memory.compressedClassSpaceSize}}m -XX:+TieredCompilation -XX:+SegmentedCodeCache -XX:Nnotallow={{.Values.jvm.memory.nonMethodCodeHeapSize}}m -XX:ProfiledCodeHeapSize={{.Values.jvm.memory.profiledCodeHeapSize}}m -XX:Nnotallow={{.Values.jvm.memory.nonProfiledCodeHeapSize}}m -XX:ReservedCodeCacheSize={{ add .Values.jvm.memory.nonMethodCodeHeapSize .Values.jvm.memory.profiledCodeHeapSize .Values.jvm.memory.nonProfiledCodeHeapSize}}m{{- end -}}

在deployment.yaml文件中使用:

- name: {{ include "chart.name" . }}
         image: "{{ .Values.contAIner.image.repository }}:{{ .Values.container.image.tag }}"
         env:
           - name: JAVA_TOOL_OPTIONS
             value: {{ include "chart.javaToolOptions" . }}

根据提供的参数自动配置内存请求和限制:

resources:
           limits:
             memory: {{ add .Values.jvm.memory.heap .Values.jvm.memory.heap .Values.jvm.memory.metaspace .Values.jvm.memory.nonMethodCodeHeapSize .Values.jvm.memory.profiledCodeHeapSize .Values.jvm.memory.nonProfiledCodeHeapSize .Values.jvm.memory.buffer | printf "%dMi"}}
             cpu: {{ .Values.container.resources.limits.cpu }}
           requests:
             memory: {{ add .Values.jvm.memory.heap .Values.jvm.memory.metaspace .Values.jvm.memory.nonMethodCodeHeapSize .Values.jvm.memory.profiledCodeHeapSize .Values.jvm.memory.nonProfiledCodeHeapSize | printf "%dMi"}}
             cpu: {{ .Values.container.resources.requests.cpu }}

结论

通过这一设置,我们将 Kubernetes 一侧的 "内存不足"(Out Of Memory)错误数量降至零。现在,JVM 会在自己这边发生 OOM,并生成堆转储,帮助开发人员分析内存。我们会发现是否有一些优化需要推进,或者我们是否需要增加堆大小(或其他内存空间)。

通过微调 JVM 内存配置,我们打破了恶性循环,即每次 OOM 都意味着为 pod 增加内存,以避免未来出现问题。我们能更好地了解每个内存空间,以及如何和何时增加它们。

Kubernetes 中的 Java 应用的内存调优

每次调整都需要测试,因此我们建议使用一些工具,例如 Micrometer,来获得有关 JVM 使用情况的一些指标。 而且,最重要的是,我们减少了应用程序的内存需求,并通过减少内存浪费事实上降低了基础设施的成本!



Tags:Kubernetes   点击:()  评论:()
声明:本站部分内容及图片来自互联网,转载是出于传递更多信息之目的,内容观点仅代表作者本人,不构成投资建议。投资者据此操作,风险自担。如有任何标注错误或版权侵犯请与我们联系,我们将及时更正、删除。
▌相关推荐
Kubernetes 究竟有没有 LTS?
从一个有趣的问题引出很多人都在关注的 Kubernetes LTS 的问题。有趣的问题2019 年,一个名为 apiserver LoopbackClient Server cert expired after 1 year[1] 的 issue 中提...【详细内容】
2024-03-15  Search: Kubernetes  点击:(6)  评论:(0)  加入收藏
Kubernetes 集群 CPU 使用率只有 13% :这下大家该知道如何省钱了
作者 | THE STACK译者 | 刘雅梦策划 | Tina根据 CAST AI 对 4000 个 Kubernetes 集群的分析,Kubernetes 集群通常只使用 13% 的 CPU 和平均 20% 的内存,这表明存在严重的过度...【详细内容】
2024-03-08  Search: Kubernetes  点击:(12)  评论:(0)  加入收藏
聊聊 Kubernetes 网络模型综合指南
这篇详细的博文探讨了 Kubernetes 网络的复杂性,提供了关于如何在容器化环境中确保高效和安全通信的见解。译自Navigating the Network: A Comprehensive Guide to Kubernete...【详细内容】
2024-02-19  Search: Kubernetes  点击:(37)  评论:(0)  加入收藏
Kubernetes是什么?主要特点是什么?
Kubernetes是什么?Kubernetes,也称为K8s,是一个开源的容器编排系统,由Google首次开发和维护。它允许容器化的应用程序在集群中自动部署、扩展和管理。Kubernetes提供了一种容器...【详细内容】
2024-02-01  Search: Kubernetes  点击:(153)  评论:(0)  加入收藏
开发者的Kubernetes懒人指南
你可以将本文作为开发者快速了解 Kubernetes 的指南。从基础知识到更高级的主题,如 Helm Chart,以及所有这些如何影响你作为开发者。译自Kubernetes for Lazy Developers。作...【详细内容】
2024-02-01  Search: Kubernetes  点击:(50)  评论:(0)  加入收藏
Kubernetes Informer基本原理,你明白了吗?
本文分析 k8s controller 中 informer 启动的基本流程不论是 k8s 自身组件,还是自己编写 controller,都需要通过 apiserver 监听 etcd 事件来完成自己的控制循环逻辑。如何高...【详细内容】
2024-01-30  Search: Kubernetes  点击:(37)  评论:(0)  加入收藏
Kubernetes 100个常用命令!
这篇文章是关于使用 Kubectl 进行 Kubernetes 诊断的指南。列出了 100 个 Kubectl 命令,这些命令对于诊断 Kubernetes 集群中的问题非常有用。这些问题包括但不限于:• 集...【详细内容】
2024-01-03  Search: Kubernetes  点击:(76)  评论:(0)  加入收藏
一文读懂Kubernetes部署策略
在这篇文章中,我们将深入研究 Kubernetes 部署概念和一些常见策略,了解每种策略的优缺点。合适的部署策略使我们能够在发布应用程序时最大限度地减少停机时间、增强客户体验并...【详细内容】
2024-01-03  Search: Kubernetes  点击:(59)  评论:(0)  加入收藏
从Kubernetes的探针到DevOps
今天在群里又看有人问如何设置 Kubernetes 的探针,感觉要补充的话太多了,结合我们在一些 DevOps 项目中痛苦的体验,今天一劳永逸的全部说完,此外,也为大家展现一下为什么 DevOps...【详细内容】
2023-12-27  Search: Kubernetes  点击:(114)  评论:(0)  加入收藏
如何基于Kubernetes运行Nacos高可用集群
Nacos(Namings and Configuration Management)是阿里巴巴开源的一个易于构建云原生应用的动态服务发现、配置管理和服务管理平台。以下是Nacos的一些主要功能和特点: 服务发现...【详细内容】
2023-12-18  Search: Kubernetes  点击:(69)  评论:(0)  加入收藏
▌简易百科推荐
Docker 和传统虚拟机有什么区别?
我有一个程序员朋友,他每年情人节都要送女朋友一台服务器。他说:“谁不想在过节当天收到一台 4核8g 的服务器呢?”“万一对方不要,我还能留着自己用。” 给他一次过节的机会,他能...【详细内容】
2024-03-26  小白debug  微信公众号  Tags:Docker   点击:(12)  评论:(0)  加入收藏
掌握Docker网络驱动程序:优化容器通信
Docker为在容器内包装、交付和运行应用程序提供了一个强大的平台,从而彻底改变了容器化。网络是容器化的重要组成部分,Docker提供了各种网络驱动程序来支持容器之间的通信以...【详细内容】
2024-03-22    51CTO  Tags:Docker   点击:(10)  评论:(0)  加入收藏
Containerd容器管理
Nginx 指定容器名称 使用 ctr container create 命令创建容器后,容器并没有处于运行状态,其只是一个静态的容器。容器基本操作容器基本操作主要是 ctr image 命令,查看命令帮...【详细内容】
2024-03-20  云原生运维圈  微信公众号  Tags:容器   点击:(13)  评论:(0)  加入收藏
如何基于Docker镜像逆向生成Dockerfile
引言你是否曾经遇到过一个想要使用的 Docker 镜像,但却无法修改以适应你的特定需求?或者你可能发现了一个喜欢的 Docker 镜像,但想要了解它是如何构建的?在这两种情况下,将 Docke...【详细内容】
2024-03-07  云原生运维圈  微信公众号  Tags:Docker   点击:(22)  评论:(0)  加入收藏
Kubernetes是什么?主要特点是什么?
Kubernetes是什么?Kubernetes,也称为K8s,是一个开源的容器编排系统,由Google首次开发和维护。它允许容器化的应用程序在集群中自动部署、扩展和管理。Kubernetes提供了一种容器...【详细内容】
2024-02-01    简易百科  Tags:Kubernetes   点击:(153)  评论:(0)  加入收藏
我们一起聊聊容器资源自愈
在企业实际在使用容器这类资源的时候,除了技术本身,要考虑的其他问题也会很多。企业管理的容器有千千万万,出于效率考虑,对于有特殊需求的容器如何进行批量创建和管理呢,这就需要...【详细内容】
2024-01-30  匠心独运维妙维效  微信公众号  Tags:容器   点击:(47)  评论:(0)  加入收藏
Docker与Docker Compose入门:释放你应用部署的威力
今天给大家介绍一项强大而有趣的技能,那就是使用 Docker 和 Docker Compose 来释放你的应用部署的威力!无论你是一名开发人员还是系统管理员,掌握这个技能都将为你的工作带来巨...【详细内容】
2024-01-17  waynblog  微信公众号  Tags:Docker   点击:(65)  评论:(0)  加入收藏
Docker镜像与容器的交互及在容器内部执行代码的原理与实践
Docker作为一种流行的容器技术,已经成为现代应用程序开发和部署的重要工具。在Docker中,镜像是构建和运行容器的基础,而容器则是基于镜像创建的可执行实例。Docker镜像与容器的...【详细内容】
2024-01-10  编程技术汇  今日头条  Tags:Docker   点击:(76)  评论:(0)  加入收藏
如何在 Ubuntu 上安装 Docker
使用 Docker 意味着开启一个新的计算领域,但如果你刚刚开始使用 Docker,安装可能看起来是一项艰巨的任务。在 Ubuntu 上安装 Docker 有两种推荐的方法: 从 Ubuntu 的仓库安装 D...【详细内容】
2024-01-04    Linux中国  Tags:Docker   点击:(124)  评论:(0)  加入收藏
从Kubernetes的探针到DevOps
今天在群里又看有人问如何设置 Kubernetes 的探针,感觉要补充的话太多了,结合我们在一些 DevOps 项目中痛苦的体验,今天一劳永逸的全部说完,此外,也为大家展现一下为什么 DevOps...【详细内容】
2023-12-27  云云众生s  微信公众号  Tags:Kubernetes   点击:(114)  评论:(0)  加入收藏
站内最新
站内热门
站内头条