※当サイトの記事には、広告・プロモーションが含まれます。

コンテナ化されたJavaアプリケーションでOutOfMemoryError(OOME)が起こる?

news.mynavi.jp

⇧ ネットワーク技術の発展の賜物ですかな、そう考えると、技術ありきの要求・要件に基づいたシステムということでしょうかね。

特定の技術にシステムの仕様を依存せざるを得ないってケースがあることが、あるあるという例ですかね。

JavaのOutOfMemoryError(OOME)とは?

JavaAPIドキュメントによりますと、

docs.oracle.com

モリー不足のためにJava Virtual Machineがオブジェクトを割り当てることができず、ガベージ・コレクタによっても使用可能なメモリーをこれ以上確保できない場合にスローされます。まるで抑制が無効になっているか、スタック・トレースへの書込みができないか、あるいはその両方であるかのように、仮想マシンによってOutOfMemoryErrorオブジェクトが構築される可能性があります。

https://docs.oracle.com/javase/jp/8/docs/api/java/lang/OutOfMemoryError.html

⇧ メモリー不足の場合に、「Java 仮想マシンJVMJava Virtual Machine)」によって引き起こされるらしい。

モリー不足と判定するのは、「Java 仮想マシンJVMJava Virtual Machine)」が行っていると考えれば良いんかね?

そも、「Java 仮想マシンJVMJava Virtual Machine)」とは?

Java仮想マシン(ジャバかそうマシン、英語Java virtual machineJava VMJVM)は、Javaバイトコードとして定義された命令セットを実行するスタック型の仮想マシンAPIやいくつかのツールとセットでJava実行環境JRE)としてリリースされている。この環境を移植することで、さまざまな環境でJavaプログラムを実行することができる。

Java仮想マシン - Wikipedia

Javaのプログラミングコードが動作するために必要なものですと。

実装系

エンタープライズ用(デスクトップ用を包含)としては、オラクルIBMHPなどの各社から実装系がリリースされている。OS上でアプリケーションとして動作する形態が一般的である。

Java仮想マシン - Wikipedia

⇧ OS上で動作するアプリケーションの1種であるというのが一般的らしい。

「アプリケーション」が動作すると、

ascii.jp

 OSの役割は、「アプリケーションソフトウェアを動かす」という説明の「動かす」にある。OSはアプリケーションをメモリーに読み込んで、実行を開始する。しかし、例えばウェブブラウザーではインターネット接続が必要だし、表計算ソフトではグラフを表示するなどのグラフィックス機能が、ゲームに至ってはGPUを制御して画面を描画するといった機能が必要になる。

ASCII.jp:「OS」はどうやってプログラムを動かしているのか? (1/2)

⇧「メモリー」が使用されますと。

「メモリー」ってのは、

In computer sciencecomputer architecture is a description of the structure of a computer system made from component parts. It can sometimes be a high-level description that ignores details of the implementation. At a more detailed level, the description may include the instruction set architecture design, microarchitecture design, logic design, and implementation.

https://en.wikipedia.org/wiki/Computer_architecture

⇧「コンピューター」を構成している1要素ですと。

「コンピューター」は何某かの「ハードウェア」に搭載されていて「メモリー」のサイズも決まっているものですと。

身近な「ハードウェア」としては、パソコン本体、スマホ本体とかが有名かと。

で、話を「アプリケーション」に戻すと、

milestone-of-se.nesuke.com

⇧「アプリケーション」で稼働する「プロセス」は「メモリー」を使用していますと。

話を整理すると、

という関係らしいと。

ようやっと、「OutOfMemoryError(OOME)」の話になりますが、

⇧ 「Java 仮想マシンJVMJava Virtual Machine)」が参照してる「メモリー」は、上図のようになってるらしい。

Java 仮想マシンJVMJava Virtual Machine)」で「メモリー」のサイズを調整できるってことですかね、勿論、「ハードウェア」に搭載されていて「メモリー」のサイズと「Java 仮想マシンJVMJava Virtual Machine)」以外で参照している「メモリー」を考慮する必要はあると思いますが。

基本的には、「Java 仮想マシンJVMJava Virtual Machine)」で設定している「メモリー」の上限を超えると「 OutOfMemoryError(OOME)」が起こるのだと思う。

cero-t.hatenadiary.jp

⇧ 上記サイト様の例だと、「JDBCJava DataBase Connectivity)」のAPIによってデータベースに保存されてる大量のデータが一度に読み込まれることで、「メモリー」の使用率が「Java 仮想マシンJVMJava Virtual Machine)」で設定している「メモリー」の上限を超えて「 OutOfMemoryError(OOME)」が起こったということらしい。

コンテナ化されたJavaアプリケーションでOutOfMemoryError(OOME)が起こる?

で、何やら、コンテナ化されたJavaアプリケーションだと、

miraitranslate-tech.hatenablog.jp

developers.redhat.com

It's clear that increasing the memory and letting the JVM set its own parameters is not always a good idea when running inside containers. When running a Java application inside containers, we should set the maximum heap size (-Xmx parameter) ourselves based on the application needs and the container limits.

https://developers.redhat.com/blog/2017/03/14/java-inside-docker

⇧ と前置きがあり、

The Java JVM until now doesn't didn't provide support to understand that it's was running inside a container and that it has some resources like those that are memory and CPU restricted. Because of that, you can't let the JVM ergonomics take the decision by itself regarding the maximum heap size.

https://developers.redhat.com/blog/2017/03/14/java-inside-docker

From JDK 8u131+ and JDK 9, there's an  experimental VM option that allows the JVM ergonomics to read the memory values from CGgroups.  To enable it on, you must explicit set the parameters -XX:+UnlockExperimentalVMOptions and -XX:+UseCGroupMemoryLimitForHeap on the JVM. You can see it in action on the following Dockerfile.

https://developers.redhat.com/blog/2017/03/14/java-inside-docker

Java 10 was released and it now has all the improvements needed to run inside a container.  Because of these improvements, the flags -XX:+UnlockExperimentalVMOptions and -XX:+UseCGroupMemoryLimitForHeap aren't needed anymore. In fact, you try to execute the JDK 10 with those parameters enabled, you will see the following warning: "Option UseCGroupMemoryLimitForHeap was deprecated in version 10.0 and will likely be removed in a future release."

https://developers.redhat.com/blog/2017/03/14/java-inside-docker

⇧ とあるように、Javaのバージョンによっては、「Java 仮想マシンJVMJava Virtual Machine)」の起動オプションを設定してあげる必要がある模様。

Java 10以上のバージョンであれば、予め良しなに設定されてるようで、開発者が配慮する必要は無いらしい。

レガシーなシステムだと、Javaのバージョンが10に満たないとかありそうなので、コンテナ化されたJavaアプリケーションについて注意が必要ということですかね...

そもそも、JavaってGC(Garbage Collection)の機能で、煩雑なメモリー管理を開発者が意識しないで済むって聞いてたような気がするんだけど...

ビジネスロジックの実装などに注力させて欲しい...

ちなみに、Dockerでのコンテナを利用しているのであれば、

knowledge.sakura.ad.jp

www.distant-view.co.jp

⇧ コンテナのメモリのサイズを変更できるようです。

まぁ、確かに「Java 仮想マシンJVMJava Virtual Machine)」のメモリのサイズの上限を上げたところで、Javaアプリケーションが動作しているコンテナのメモリのサイズの上限が上がってなかったら、意味無いものね...

コンテナのメモリー管理、煩わしい...

コンテナのメモリーの使用率を検知して、自動でコンテナのメモリーの上限をリサイズしてくれるサービス無いのかしらね...

イメージとしては、

xtech.nikkei.com

 垂直スケーリングは、負荷の増減に合わせて、高性能/低性能のマシンに変更することです。負荷増のときは、高性能なCPU、大容量のメモリーに変更します。これを「スケールアップ」、逆は「スケールダウン」と呼びます。

スケーリング(2ページ目) | 日経クロステック(xTECH)

⇧ コンテナで稼働してるアプリケーションを停止することなく、「垂直スケーリング」をコンテナで実現してくれるようなのが欲しい...

でも、ベアメタルな物理サーバーでアプリケーションを稼働させてるオンプレミスな環境だと、クラウドみたいなサービスを利用できるか分からんから自力で何とかせにゃならんのか...

docker composeとDockerのバージョンによっても、

reboooot.net

但し –compatibility を本番では使うことは非推奨となっておりますのでご注意を!
https://docs.docker.com/compose/compose-file/compose-versioning/#compatibility-mode

docker-compose におけるメモリ使用量の制限方法 - reboooot․net

zaki-hmkc.hatenablog.com

⇧ 対応が変わってくるらしい。

と言うか、docker compose v3だと本番環境で利用することはできないんか...本番環境でコンテナのメモリーのサイズの上限を設定したい場合、どうすれば良いのか...

そもそも、最新のドキュメントだと、「compability」というオプションの説明が無い...

docs.docker.jp

swarm モードではないコンテナのリソース制限のオプションを探していますか?

ここで説明しているオプションは、 deploy キーと swarm モードでの指定です。もしも swarm 以外のデプロイ対象に対してリソース制限を設定したいのであれば、 Compose ファイル形式バージョン2の CPU、メモリ、その他リソースに関するオプション を使います。

https://docs.docker.jp/compose/compose-file/compose-file-v3.html#resources

⇧ あと、「swarm モード」かそうでないかで対応変わってくるらしい...

本番環境でも利用できるコンテナのメモリーのサイズ上限の設定が知りたいだけなんだが...

何やら、docker compose v3のメモリーのサイズ設定に関する仕様については、

github.com

⇧ 侃侃諤諤という感じでしょうか...

2022年12月に投稿された意見で、

github.com

⇧ 上記を参照しろとなってるけど、本番環境でもメモリーのサイズ上限の設定できるってことで良いんだろうか?

そも、Dockerのドキュメントによると、

docs.docker.jp

OOME に起因する不安定リスクを回避するには、以下の対応があります。

  • アプリケーションの本番環境への移行前に、アプリケーションがどのようにメモリを必要とするかをテストして理解すること。

  • アプリケーションが、一定のリソースがあればホスト上だけで動作することを確認すること。

  • これ以降に示すような、コンテナのメモリ使用量を制限すること。

  • Docker ホスト上のスワップの設定に十分注意すること。 スワップはメモリに比べて、処理速度が遅く性能が劣ります。 ただしシステム・メモリの不足を補うためのバッファを利用します。

  • コンテナを サービス に変更する検討をすること。 そしてサービス・レベルでの制約やノード・ラベルを利用することで、十分なメモリを有するホスト上でのみアプリケーションが動作するように検討すること。

https://docs.docker.jp/config/containers/resource_constraints.html#understand-the-risks-of-running-out-of-memory

⇧ アプリケーションが必要なメモリを確認する必要があるらしいので、アプリケーションで使用するメモリの上限を決め打ちする必要があるっぽいですな。

なるほど、性能試験が必要なわけだ。

要するに、これ以上のサイズのデータについての処理が失敗するとしても責任を持てませんよ、っていう部分を非機能要件で定めて、それを性能試験で検証するってことなのね。

「性能試験」と「負荷テスト」の違いについては、

zenn.dev

⇧ 上記サイト様がまとめてくださっています。

ちなみに、Wikipediaさんによりますと、

パフォーマンステスト (: performance testing)はソフトウェア品質保証では、一般に、特定のワークロードでの応答性と安定性の観点からシステムがどのように機能するかを判断するために実行されるテスト手法である。

ソフトウェアパフォーマンステスト - Wikipedia

種類

負荷テスト

負荷テストは、パフォーマンステストの最も単純な形式である。負荷テストは通常、特定の予想される負荷の下でのシステムの動作を理解するために実行される。

ソフトウェアパフォーマンステスト - Wikipedia

⇧「負荷テスト」は「性能試験」の1種とする説明になっているので、このあたり、認識齟齬が起こり得そう...

モリーのサイズの上限は「負荷テスト」で確認する感じになるっぽいですね。

コンテナ化されたJavaアプリケーションのケースだと、サーバーが搭載してるメモリーから、

などを考慮して、メモリーのサイズを割り振っていって、処理できる上限サイズのデータを算出する感じになるってことかしらね。

勿論、上記以外にも、サーバーが搭載しているメモリーを使用しているプロセスを網羅しておく必要はあるのだけど。

尚更、docker compose v3における本番環境でのメモリーの上限サイズの設定が可能かどうかをハッキリさせて欲しい...

Kubernetesとかなら、モヤモヤしなくて済むんかな?

Dockerにおけるメモリーの上限サイズの推奨構成が分からんのだけど、

docs.docker.jp

デフォルトにおいてコンテナには、リソースの利用に関して制限がありません。 したがってホスト・カーネルのスケジューラが割り振るリソースを、その分だけ利用できます。 Docker には、コンテナーが利用するメモリや CPU をどれくらいにするかを制御する方法があります。

https://docs.docker.jp/config/container/resource_constraints.html

⇧ デフォルトの設定だと、コンテナのメモリーの上限サイズは設けられていない模様。

もしかして、Dockerのホストの方のメモリーのサイズを多めにとって、コンテナーのメモリーの上限サイズの設定はしない方が良いんかね?

う~む、メモリー周りの設定のベストプラクティスが分からん...

インフラ周りの知見が欲しいな...

毎度モヤモヤ感が半端ない...

今回はこのへんで。