1概要

Javaの

Strings

は、

String

の文字を含む

char[]

で内部的に表されます。また、すべての

char

は2バイトで構成されています。これは、

Javaが内部でUTF-16を使用しているためです。

たとえば、

String

に英語の単語が含まれている場合、ASCII文字は1バイトを使用して表すことができるので、先頭の8ビットはすべての

char

に対して0になります。

多くの文字はそれらを表現するために16ビットを必要としますが、統計的に最も必要とされるのは8ビットだけです – LATIN-1文字表現。そのため、メモリの消費量とパフォーマンスを向上させる余地があります。

また重要なことは、

__ String


が通常JVMヒープスペースの大部分を占めることです。そして、それらがJVMによって保存される方法のために、ほとんどの場合、

String__インスタンスは実際には2倍のスペースを必要とする可能性があります。

この記事では、JDK 6で導入されたCompressed Stringオプションと、JDK 9で最近導入された新しいCompact Stringについて説明します。どちらもJMVの文字列のメモリ消費を最適化するように設計されています。


2圧縮

String

– Java 6

JDK 6 update 21 Performance Releaseでは、新しいVMオプションが導入されました。

-XX:+UseCompressedStrings

このオプションを有効にすると、

Strings



char[]–

ではなく

byte[]

として格納されるため、メモリを節約できます。ただし、このオプションはJDK 7では最終的に削除されました。これは、意図しないパフォーマンスへの影響があるためです。


3コンパクト

String

– Java 9

Java 9はコンパクトな文字列の概念をもたらしました。

これは、

String

のすべての文字がbyte-LATIN-1表現を使用して表現できる場合は、

String

を作成するたびに、内部でバイト配列が使用され、1文字に1バイトが与えられることを意味します。

他のケースでは、もしある文字がそれを表現するのに8ビット以上を必要とするなら、すべての文字はそれぞれ2バイトを使って格納されます – UTF-16表現。

したがって、基本的には、可能な限り、各文字に1バイトを使用するだけです。

さて、問題は、すべての

String

操作がどのように機能するのでしょうか。 LATIN-1表現とUTF-16表現をどのように区別しますか?

  • さて、この問題に取り組むために、

    String

    の内部実装に別の変更が加えられました。この情報を保持する最後のフィールド

    coder

    があります。


3.1. Java 9

での

String

の実装

今までは、

String



char[]

として格納されていました。

private final char[]value;

これからは

byte[]:

になります

private final byte[]value;

変数

coder

:

private final byte coder;


coder

は次のようになります。

static final byte LATIN1 = 0;
static final byte UTF16 = 1;

ほとんどの

String

操作は、コーダーをチェックし、特定の実装にディスパッチします。

public int indexOf(int ch, int fromIndex) {
    return isLatin1()
      ? StringLatin1.indexOf(value, ch, fromIndex)
      : StringUTF16.indexOf(value, ch, fromIndex);
}

private boolean isLatin1() {
    return COMPACT__STRINGS && coder == LATIN1;
}

JVMに準備ができて利用可能である必要があるすべての情報があるので、

CompactString

VMオプションはデフォルトで有効になっています。無効にするには、次のようにします。

+XX:-CompactStrings


3.2.

coder

のしくみ

Java 9の

String

クラスの実装では、長さは次のように計算されます。

public int length() {
    return value.length >> coder;
}


String

にLATIN-1しか含まれていない場合、

coder

の値は0になり、

String

の長さはバイト配列の長さと同じになります。

それ以外の場合、

String

がUTF-16表現の場合、

coder

の値は1になるため、長さは実際のバイト配列の半分のサイズになります。

  • Compact

    String、

    に対して行われたすべての変更は

    String

    クラスの内部実装にあり、

    String

    usersを使用する開発者にとっては完全に透過的です。


4コンパクト

文字列

と圧縮

文字列


JDK 6 Compressed

Stringsの場合、

String

コンストラクタが

char[]

のみを引数として受け入れたという大きな問題がありました。これに加えて、多くの

String

操作はバイト配列ではなく

char[]__表現に依存していました。このため、多くの解凍を行わなければならず、パフォーマンスに影響を及ぼしました。

Compact

Stringの場合は、

追加フィールドの “coder”を維持することでオーバーヘッドも増加する可能性があります。

coder

のコストと

__byte


sから


char


sへのアンパック(UTF-16表現の場合)を軽減するために、いくつかのメソッドはhttps://en.wikipedia.org/wiki/Intrinsic

function[intrinsified]と生成されたASMコードです。 JITコンパイラによるものも改善されています。

この変更により、直感に反する結果が生じました。 LATIN-1

indexOf(String)

は組み込みメソッドを呼び出しますが、

indexOf(char)

は呼び出しません。 UTF-16の場合、これらのメソッドはどちらも組み込みメソッドを呼び出します。この問題はLATIN-1

String

のみに影響し、将来のリリースで修正される予定です。

したがって、パフォーマンスの面では、コンパクトな弦は圧縮された弦より優れています。

コンパクト__文字列を使用して節約されたメモリ量を確認するために、さまざまなJavaアプリケーションヒープダンプが分析されました。そして、結果は特定のアプリケーションに大きく依存していましたが、全体的な改善はほぼ常にかなりのものでした。


4.1. 性能の違い

コンパクト__文字列の有効化と無効化のパフォーマンスの違いの非常に単純な例を見てみましょう。

long startTime = System.currentTimeMillis();

List strings = IntStream.rangeClosed(1, 10__000__000)
  .mapToObj(Integer::toString)
  .collect(toList());

long totalTime = System.currentTimeMillis() - startTime;
System.out.println(
  "Generated " + strings.size() + " strings in " + totalTime + " ms.");

startTime = System.currentTimeMillis();

String appended = (String) strings.stream()
  .limit(100__000)
  .reduce("", (l, r) -> l.toString() + r.toString());

totalTime = System.currentTimeMillis() - startTime;
System.out.println("Created string of length " + appended.length()
  + " in " + totalTime + " ms.");

ここでは、1000万個の

__String

__を作成してから、それらを単純な方法で追加しています。このコードを実行すると(デフォルトでコンパクトストリングが有効になっています)、次のような出力が得られます。

Generated 10000000 strings in 854 ms.
Created string of length 488895 in 5130 ms.

同様に、以下を使用してコンパクト文字列を無効にして実行したとします。


-XX:-CompactStrings

オプション、出力は次のとおりです。

Generated 10000000 strings in 936 ms.
Created string of length 488895 in 9727 ms.

明らかに、これは表面レベルのテストであり、非常に代表的なものではありません。これは、この特定のシナリオでパフォーマンスを向上させるために新しいオプションが実行できることのほんの一部にすぎません。


5結論

このチュートリアルでは、メモリ効率の良い方法で

__String

__を格納することによって、JVM上のパフォーマンスとメモリ消費を最適化する試みを見ました。

いつものように、コード全体はhttps://github.com/eugenp/tutorials/tree/master/java-strings[Githubで利用可能]です。