[解決済み] Javaがヒープサイズ(またはDockerのメモリ制限を満たすサイズ)よりはるかに多くのメモリを使用する。
質問
私のアプリケーションでは、Javaプロセスによって使用されるメモリがヒープサイズよりもはるかに多くなっています。
コンテナが動作しているシステムでは、コンテナがヒープサイズよりもはるかに多くのメモリを消費しているため、メモリの問題が発生し始めます。
ヒープサイズは128MBに設定されています(
-Xmx128m -Xms128m
)、コンテナは最大1GBのメモリを使用します。通常の場合、500MB必要です。もし、ドッカーコンテナに以下のような制限がある場合(例えば
mem_limit=mem_limit=400MB
OSのメモリ不足キラーによってプロセスが強制終了されます。
Java プロセスがヒープよりはるかに多くのメモリを使用している理由を説明してもらえますか?Dockerのメモリ制限を正しくサイズする方法は?Javaプロセスのオフヒープ・メモリ・フットプリントを削減する方法はありますか?
私は、以下のコマンドを使用して問題の詳細を収集します。 JVMのネイティブメモリ追跡 .
ホストシステムから、コンテナが使用するメモリを取得する。
$ docker stats --no-stream 9afcb62a26c8
CONTAINER ID NAME CPU % MEM USAGE / LIMIT MEM % NET I/O BLOCK I/O PIDS
9afcb62a26c8 xx-xxxxxxxxxxxxx-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx.0acbb46bb6fe3ae1b1c99aff3a6073bb7b7ecf85 0.93% 461MiB / 9.744GiB 4.62% 286MB / 7.92MB 157MB / 2.66GB 57
コンテナの中から、プロセスが使用しているメモリを取得します。
$ ps -p 71 -o pcpu,rss,size,vsize
%CPU RSS SIZE VSZ
11.2 486040 580860 3814600
$ jcmd 71 VM.native_memory
71:
Native Memory Tracking:
Total: reserved=1631932KB, committed=367400KB
- Java Heap (reserved=131072KB, committed=131072KB)
(mmap: reserved=131072KB, committed=131072KB)
- Class (reserved=1120142KB, committed=79830KB)
(classes #15267)
( instance classes #14230, array classes #1037)
(malloc=1934KB #32977)
(mmap: reserved=1118208KB, committed=77896KB)
( Metadata: )
( reserved=69632KB, committed=68272KB)
( used=66725KB)
( free=1547KB)
( waste=0KB =0.00%)
( Class space:)
( reserved=1048576KB, committed=9624KB)
( used=8939KB)
( free=685KB)
( waste=0KB =0.00%)
- Thread (reserved=24786KB, committed=5294KB)
(thread #56)
(stack: reserved=24500KB, committed=5008KB)
(malloc=198KB #293)
(arena=88KB #110)
- Code (reserved=250635KB, committed=45907KB)
(malloc=2947KB #13459)
(mmap: reserved=247688KB, committed=42960KB)
- GC (reserved=48091KB, committed=48091KB)
(malloc=10439KB #18634)
(mmap: reserved=37652KB, committed=37652KB)
- Compiler (reserved=358KB, committed=358KB)
(malloc=249KB #1450)
(arena=109KB #5)
- Internal (reserved=1165KB, committed=1165KB)
(malloc=1125KB #3363)
(mmap: reserved=40KB, committed=40KB)
- Other (reserved=16696KB, committed=16696KB)
(malloc=16696KB #35)
- Symbol (reserved=15277KB, committed=15277KB)
(malloc=13543KB #180850)
(arena=1734KB #1)
- Native Memory Tracking (reserved=4436KB, committed=4436KB)
(malloc=378KB #5359)
(tracking overhead=4058KB)
- Shared class space (reserved=17144KB, committed=17144KB)
(mmap: reserved=17144KB, committed=17144KB)
- Arena Chunk (reserved=1850KB, committed=1850KB)
(malloc=1850KB)
- Logging (reserved=4KB, committed=4KB)
(malloc=4KB #179)
- Arguments (reserved=19KB, committed=19KB)
(malloc=19KB #512)
- Module (reserved=258KB, committed=258KB)
(malloc=258KB #2356)
$ cat /proc/71/smaps | grep Rss | cut -d: -f2 | tr -d " " | cut -f1 -dk | sort -n | awk '{ sum += $1 } END { print sum }'
491080
アプリケーションは、Jetty/Jersey/CDIを使用したWebサーバーで、36MBのファットファー内にバンドルされています。
OSとJavaは以下のバージョンを使用しています(コンテナ内)。Dockerイメージは
openjdk:11-jre-slim
.
$ java -version
openjdk version "11" 2018-09-25
OpenJDK Runtime Environment (build 11+28-Debian-1)
OpenJDK 64-Bit Server VM (build 11+28-Debian-1, mixed mode, sharing)
$ uname -a
Linux service1 4.9.125-linuxkit #1 SMP Fri Sep 7 08:20:28 UTC 2018 x86_64 GNU/Linux
https://gist.github.com/prasanthj/48e7063cac88eb396bc9961fb3149b58
解決方法は?
Javaプロセスで使用される仮想メモリは、Javaヒープだけでなく、はるかに拡張されています。ご存知のように、JVMは多くのサブシステムを含んでいます。ガベージコレクタ、クラスローディング、JITコンパイラなど、これらのすべてのサブシステムは、機能するために一定の量のRAMを必要とします。
RAMを消費するのはJVMだけではありません。ネイティブ・ライブラリ(標準的なJavaクラス・ライブラリを含む)もまた、ネイティブ・メモリを割り当てるかもしれません。そして、これは、Native Memory Trackingにさえ見えません。Javaアプリケーション自体も、直接ByteBufferによって、オフヒープメモリを使用することができます。
では、Javaのプロセスでは、何がメモリを消費するのでしょうか。
JVM部分(主にNative Memory Trackingで表示されます。)
- Javaヒープ
一番わかりやすいところ。Javaオブジェクトが住んでいる場所です。ヒープには、最大で
-Xmx
のメモリ量になります。
- ガーベッジコレクタ
GCの構造およびアルゴリズムは、ヒープ管理のために追加のメモリを必要とします。これらの構造体は、マークビットマップ、マークスタック(オブジェクトグラフをトラバースするため)、リメンバードセット(領域間参照を記録するため)、その他です。それらのいくつかは直接調整可能で、例えば
-XX:MarkStackSizeMax
また、ヒープレイアウトに依存するものもあり、例えば、より大きなG1領域(
-XX:G1HeapRegionSize
)、小さいものは記憶されたセットです。
GCメモリのオーバーヘッドは、GCアルゴリズムによって異なる。
-XX:+UseSerialGC
と
-XX:+UseShenandoahGC
は、オーバーヘッドが最も小さい。G1やCMSはヒープサイズの10%程度を簡単に使用することができます。
- コードキャッシュ
動的に生成されたコードが含まれる。JIT コンパイルされたメソッド、インタプリタ、ランタイムスタブなど、動的に生成されたコードが含まれます。そのサイズは
-XX:ReservedCodeCacheSize
(デフォルトでは240M)。をオフにします。
-XX:-TieredCompilation
を使用すると、コンパイルされたコードの量が減り、その結果コードキャッシュの使用量も減ります。
- コンパイラ
JITコンパイラ自体も、その仕事をするためにメモリを必要とします。これは、Tiered Compilationをオフにするか、コンパイラのスレッド数を減らすことで再度削減することができます。
-XX:CICompilerCount
.
- クラスの読み込み
クラスのメタデータ(メソッドのバイトコード、シンボル、定数プール、アノテーションなど)は、Metaspaceというオフヒープ領域に格納されます。クラスが多くロードされればされるほど、メタスペースはより多く使用されます。使用量を制限するには
-XX:MaxMetaspaceSize
(デフォルトでは無制限)と
-XX:CompressedClassSpaceSize
(デフォルトでは1G)。
- シンボルテーブル
JVMの2つの主要なハッシュテーブル:シンボル・テーブルは、名前、署名、識別子などを含み、ストリング・テーブルは、内部文字列への参照を含む。Native Memory TrackingがStringテーブルによる大幅なメモリ使用を示す場合、それはおそらく、アプリケーションが過度に
String.intern
.
- スレッド
スレッドスタックはRAMを占有する役割も担っています。スタックの大きさは
-Xss
. デフォルトは1スレッドあたり1Mですが、幸いなことにそれほど悪い状況にはなっていません。OSはメモリページを遅延的に、つまり最初に使用するときに割り当てますので、実際のメモリ使用量はもっと少なくなります(通常、スレッドスタックあたり80-200KB)。私は
スクリプト
を使用して、RSSがJavaスレッドスタックにどれだけ属しているかを推定しています。
他にもネイティブ・メモリを確保するJVMのパーツはありますが、通常、総メモリ消費量に大きな役割を果たすことはありません。
ダイレクトバッファ
アプリケーションは、明示的にオフヒープメモリを要求するために
ByteBuffer.allocateDirect
. デフォルトのオフヒープの上限は
-Xmx
で上書きすることができます。
-XX:MaxDirectMemorySize
. Direct ByteBuffer は
Other
セクションを作成します(または
Internal
JDK 11以前)。
使用されたダイレクトメモリの量は、JConsole や Java Mission Control などの JMX を通して見ることができます。
直接のByteBufferの他に、以下のようなものがあります。
MappedByteBuffers
- プロセスの仮想メモリにマッピングされたファイルです。NMTはこれらを追跡しませんが、MappedByteBufferは物理メモリを占有することもできます。そして、それらが取ることができる量を制限する簡単な方法はありません。プロセスのメモリマップを見れば、実際の使用量を確認できます。
pmap -x <pid>
Address Kbytes RSS Dirty Mode Mapping
...
00007f2b3e557000 39592 32956 0 r--s- some-file-17405-Index.db
00007f2b40c01000 39600 33092 0 r--s- some-file-17404-Index.db
^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^
ネイティブライブラリ
で読み込まれるJNIコード
System.loadLibrary
は、JVM側からの制御なしに、望むだけのオフヒープ・メモリを割り当てることができます。これは、標準的なJavaクラスライブラリにも関係します。特に、クローズドでないJavaリソースは、ネイティブ・メモリ・リークの原因となる可能性があります。典型的な例としては
ZipInputStream
または
DirectoryStream
.
JVMTIエージェントは特に。
jdwp
デバッギング・エージェントもまた、過剰なメモリ消費を引き起こす可能性があります。
この回答 を使用してネイティブメモリ割り当てをプロファイルする方法について説明します。 非同期プロファイラ .
アロケーターの問題
プロセスは通常、ネイティブメモリをOSに直接要求するか、(
mmap
システムコール)、または
malloc
- 標準の libc アロケータを使用します。順番に
malloc
を使用してOSに大きなメモリチャンクを要求します。
mmap
そして、これらのチャンクを独自のアロケーションアルゴリズムに従って管理します。問題は、このアルゴリズムがフラグメンテーションを引き起こす可能性があることです。
過剰な仮想メモリ使用
.
jemalloc
は、代替アロケータで、しばしば通常の libc よりも賢く見えます。
malloc
に変更することです。
jemalloc
は、無料でフットプリントが小さくなる可能性があります。
まとめ
Javaプロセスの完全なメモリ使用量を推定する保証された方法はありません。
Total memory = Heap + Code Cache + Metaspace + Symbol tables +
Other JVM structures + Thread stacks +
Direct buffers + Mapped files +
Native Libraries + Malloc overhead + ...
JVMフラグによって、特定のメモリ領域(コードキャッシュなど)を縮小したり制限したりすることは可能ですが、他の多くの領域はJVMの制御からまったく外れています。
Dockerの制限を設定するための1つの可能なアプローチは、プロセスの"normal"状態での実際のメモリ使用量を監視することです。Javaメモリ消費に関する問題を調査するためのツールやテクニックがあります。 ネイティブメモリトラッキング , pmap , ジェマロク , 非同期プロファイラ .
更新情報
私のプレゼンテーションの録画はこちらです。 Java プロセスのメモリフットプリント .
このビデオでは、Javaプロセスでメモリを消費する可能性のあるもの、特定のメモリ領域のサイズを監視して抑制する方法、およびJavaアプリケーションのネイティブ・メモリ・リークをプロファイルする方法について説明します。
関連
-
この行に複数のマーカーがある - HttpServletResponseが型エラーに解決できない
-
Dateが型に解決できない問題を解決する
-
Enumとの組み合わせでswitchの使い方を一度覚えるために必要な定数式
-
Eclipseプロンプトを実行する java仮想マシンを使用しない
-
サーブレットクラスのインスタンス化エラーの解決法
-
が 'X-Frame-Options' を 'deny' に設定しているため、フレーム内にある。
-
起動時にEclipseエラーが発生しました。起動中に内部エラーが発生しました。java.lang.NullPoin: "Javaツーリングの初期化 "中に内部エラーが発生しました。
-
Java(1)仕上げの基本概念+eclipseのインストール構成
-
[解決済み] Dockerで1つ以上のポートを公開するには?
-
[解決済み】LinuxでJavaの仮想メモリ使用量、メモリ使用量が多すぎる。
最新
-
nginxです。[emerg] 0.0.0.0:80 への bind() に失敗しました (98: アドレスは既に使用中です)
-
htmlページでギリシャ文字を使うには
-
ピュアhtml+cssでの要素読み込み効果
-
純粋なhtml + cssで五輪を実現するサンプルコード
-
ナビゲーションバー・ドロップダウンメニューのHTML+CSSサンプルコード
-
タイピング効果を実現するピュアhtml+css
-
htmlの選択ボックスのプレースホルダー作成に関する質問
-
html css3 伸縮しない 画像表示効果
-
トップナビゲーションバーメニュー作成用HTML+CSS
-
html+css 実装 サイバーパンク風ボタン
おすすめ
-
スタイルシートとして解釈されるリソースが、MIMEタイプtext/htmlで転送される。
-
undefinedeclipse エラー。この行に複数のアノテーションが見つかりました: - 文字列を型解決に解決できない
-
eclipse で「アクセス制限: タイプ 'HttpServer' は API ではありません」というプロンプトが表示される。
-
eclipse アクセス制限です。タイプ 'xxx' は API ではありません(必須ライブラリ '' の制限)。
-
をインスタンス化することができません。
-
Eclipseプロンプトを実行する java仮想マシンを使用しない
-
リソースの読み込みに失敗しました。サーバーはステータス500(内部サーバーエラー)で応答しました。
-
マスキング このリソースにアクセスするには、完全な認証が必要です。
-
javaでクラスを作成すると、enclosing classでないように見える
-
mavenプロジェクトのテストエラー java.lang.ClassNotFoundException: org.glassfish.jersey.client.ClientConfig の問題を解決する。