JVMでの圧縮されたOOP

  • link:/category/architecture/ [アーキテクチャ]

  • Java

  • JVM

1. 概要

JVMがメモリを管理します。 これにより、開発者のメモリ管理の負担が軽減されるため、*オブジェクトポインタを手動で操作する必要がありません*。これは時間がかかり、エラーが発生しやすいことが証明されています。
内部では、JVMにはメモリ管理プロセスを最適化するための多くの巧妙なトリックが組み込まれています。 * 1つの秘は、この記事で評価する_ * Compressed Pointers *、_の使用です。 まず、JVMが実行時にオブジェクトをどのように表すかを見てみましょう。

2. ランタイムオブジェクト表現

HotSpot JVMは、__ oop__sまたは_Ordinary Object Pointers_と呼ばれるデータ構造を使用してオブジェクトを表します。 これらの_oops_は、ネイティブCポインターと同等です。 * __instanceOops __は、Java *のオブジェクトインスタンスを表す特別な種類の__oop __です。 さらに、JVMは、http://hg.openjdk.java.net/jdk8/jdk8/hotspot/file/87ee5ee27509/src/share/vm/oops/ [OpenJDKソースツリーに保持されている他のいくつかの_oops_ supportsもサポートしています。 ]。
JVMが__instanceOops ___inメモリをどのようにレイアウトするかを見てみましょう。

* 2.1。 オブジェクトメモリレイアウト*

_instanceOop_のメモリレイアウトは単純です。オブジェクトヘッダーの直後にインスタンスフィールドへの0個以上の参照が続きます。
オブジェクトヘッダーのJVM表現は、次のもので構成されます。
  • * Biasedなどの多くの目的に役立つ1つのマークワード*
    Locking
    Identityハッシュ値、_および_GC。 これは_oopではありませんが、歴史的な理由により、http://hg.openjdk.java.net/jdk8/jdk8/hotspot/file/87ee5ee27509/src/share/vm/oops/markOop.hpp [OpenJDK’s oop]ソースツリー。

  • クラスメタデータへのポインタを表す1つのクラス語
    Java 7以前は、https://www.baeldung.com/native-memory-tracking-in-jvm [Permanent Generation]で開催されていましたが、Java 8以降では、https://www.baeldungにあります。 .com / native-memory-tracking-in-jvm [Metaspace] .

  • * 32ビット長* ワードを表す配列のみのフィールド
    配列の長さ。

  • 32ビットのギャップ**オブジェクトのアライメントを強制します。 これは物事を作ります
    後で見るように、簡単です。

    *ヘッダーの直後に、インスタンスフィールドへの参照が0個以上あります。*この場合、_word_はネイティブマシンワードであるため、レガシー32ビットマシンでは32ビット、最新システムでは64ビットです。

2.2。 廃棄物の解剖

従来の32ビットアーキテクチャから、より最新の64ビットマシンに切り替えるとします。 最初は、すぐにパフォーマンスが向上することが予想されます。 ただし、JVMが関係する場合は常にそうとは限りません。
*このパフォーマンス低下の主な原因は64ビットオブジェクト参照です。* 64ビット参照は32ビット参照の2倍のスペースを占有するため、一般にメモリ消費量が増え、GCサイクルがより頻繁になります。 GCサイクルに専念する時間が長いほど、アプリケーションスレッドのCPU実行スライスが少なくなります。
それでは、これらの32ビットアーキテクチャに切り替えて再度使用する必要がありますか? これがオプションであったとしても、32ビットプロセススペースに4 GBを超えるヒープスペースを確保するには、もう少し作業が必要です。

*3. 圧縮されたOOP *

結局のところ、JVMはオブジェクトポインターまたは__oopsを圧縮することでメモリの浪費を回避できます。__so両方の長所を活用できます。

3.1。 基本的な最適化

前述したように、JVMはオブジェクトにパディングを追加して、サイズが8バイトの倍数になるようにします。 *これらのパディングでは、__oops ___の最後の3ビットは常にゼロです。*これは、8の倍数である数値が常にバイナリで_000_で終わるためです。
画像:https://www.baeldung.com/uploads/Untitled-Diagram-2-e1554193172973-100x28.jpg%20100w [image]
JVMは既に最後の3ビットが常にゼロであることをすでに知っているため、ヒープにこれらの重要でないゼロを格納しても意味がありません。 代わりに、それらがそこにあると想定し、以前は32ビットに収まらなかった他の3つの重要なビットを格納します。 これで、3つの右シフトされたゼロを持つ32ビットアドレスがあるため、35ビットポインターを32ビットポインターに圧縮しています。 *これは、64ビット参照を使用せずに最大32 GB – 2 ^ 32 + 3 ^ = 2 ^ 35 ^ = 32 GB –のヒープスペースを使用できることを意味します。*
この最適化を機能させるために、JVMがメモリ内のオブジェクトを見つける必要がある場合、*ポインタを左に3ビットシフトします*(基本的に、これらの3ゼロを最後に追加します)。 一方、ヒープへのポインターをロードすると、JVMはポインターを右に3ビットシフトして、以前に追加されたゼロを破棄します。 **基本的に、JVMはスペースを節約するためにもう少し計算を実行します。 **幸いなことに、ビットシフトはほとんどのCPUにとって非常に簡単な操作です。
__oop __compressionを有効にするには、__- XX:+ UseCompressedOops __tuningフラグを使用できます。 __oop __compressionは、最大ヒープサイズが32 GB未満の場合のJava 7以降のデフォルトの動作です。 *最大ヒープサイズが32 GBを超える場合、JVMは__oop __compressionを自動的にオフにします。* 32 Gbヒープサイズを超えるメモリ使用率は、異なる方法で管理する必要があります。

3.2。 32 GBを超える

Javaヒープサイズが32GBを超える場合、圧縮ポインタを使用することもできます。 *デフォルトのオブジェクトアライメントは8バイトですが、この値はis __- XX:ObjectAlignmentInBytes __tuningフラグを使用して構成可能です。 指定する値は2のべき乗で、8〜256 *の範囲内でなければなりません。
次のように、圧縮ポインタを使用して最大可能ヒープサイズを計算できます。
4 GB * ObjectAlignmentInBytes
たとえば、オブジェクトのアライメントが16バイトの場合、圧縮ポインターで最大64 GBのヒープスペースを使用できます。
アラインメント値が増加すると、オブジェクト間の未使用スペースも増加することに注意してください。 その結果、圧縮されたポインタを大きなJavaヒープサイズで使用してもメリットが得られない場合があります。

3.3。 未来のGC

link:/jvm-zgc-garbage-collector[ZGC]は、https://openjdk.java.net/jeps/333 [Java 11]に新しく追加された、実験的でスケーラブルなレイテンシーガベージコレクター。 GCの一時停止を10ミリ秒未満に保ちながら、さまざまな範囲のヒープサイズを処理できます。 ZGCはhttps://youtu.be/kF_r3GE3zOo?t=643[64ビットの色付きポインター]を使用する必要があるため、**圧縮参照をサポートしていません*。 そのため、ZGCのような超低レイテンシGCを使用することは、より多くのメモリを使用して再度トレードオフする必要があります。

4. 結論

この記事では、* 64ビットアーキテクチャでの* JVMメモリ管理の問題*について説明しました。 *圧縮されたポインターとオブジェクトのアライメント*を見て、JVMがこれらの問題にどのように対処し、無駄の少ないポインターと最小限の追加計算でより大きなヒープサイズを可能にするかを見ました。