Java 12 〜ラッパークラスのキャッシュについて〜

GMOアドマーケティングのT.Nです。

今年も弊社にエンジニアの新入社員が入ってきました。
新入社員研修の一つとして、Java研修も実施しました。

Java研修では、「Java本格入門」という本でJavaの基礎を学びながら、
Servlet/JSPでWebアプリケーションの開発を行いました。

研修を進めていく中で、本に記載されている以下の内容について、
なぜ効率に違いがあるのかという質問を受けました。

引用元: 技術評論社「Java本格入門」69ページ

これは、valueOfを実行した場合は、
内部でキャッシュしたデータを返しているためなのですが、
その仕組みが気になったので、
今回は、Integerクラスのキャッシュと、
その他のラッパークラスのキャッシュについて調べてまとめました。

ちなみに、今回はJDK 12のソースコードを調べました。
JDK 11からJDK 12で、Cacheに関係する内部クラスの処理に変更がありました。

例えば、Integerの場合は、
IntegerCacheというクラスでキャッシュしているのですが、
IntegerCacheの初期化時に、以下のような処理が追加されています。

これは、CDS(Class-Data Sharing)という仕組みを利用していて、
アーカイブからIntegerCacheのクラスデータを取得できる場合はそちらのデータを使用し、
取得できない場合は、IntegerCacheのフィールドの生成処理を実行するようになっています。

JDK 12で、JEP 341のDefault CDS Archivesがリリースされたことで、
IntegerCacheもデフォルトでアーカイブされるようになったのだと思います。

JDK 12での変更点にも触れながら、
各ラッパークラスのキャッシュについて紹介していきます。

ラッパークラス一覧

以下のクラスが、Javaのラッパークラスです。

  • Integer
  • Long
  • Double
  • Float
  • Boolean
  • Byte
  • Short
  • Character

Integerについて

valueOfの実装は以下のようになっています。

引数の値が-128から127の場合は、キャッシュの値を使用し、
それ以外の場合は、新しくIntegerオブジェクトを生成しています。

-128から127の範囲外の場合はどちらを使用しても良さそうですが、
Java 9からIntegerのコンストラクターに@Deprecatedがつけられたので、
範囲外の場合もvalueOfを使うのが良いです。

ちなみに、-128から127という範囲は、
最大値のみ、以下のようにVMオプションで値を指定することで変更可能です。

Longについて

LongでもIntegerと同じように内部でオブジェクトをキャッシュしています。
範囲は-128から127で固定されていて、Integerのように最大値を指定することはできません。

ちなみに、JDK 11では、以下のようなシンプルな処理になっています。

DoubleとFloatについて

DoubleとFloatでは、
Javadocに、頻繁に要求される値はキャッシュされるというような記載がありますが、
実際はキャッシュされておらず、
valueOfの中で新しいインスタンスを生成して返しています。

Integerなどの他のラッパークラスと同じように、
Java 9からコンストラクターに@Deprecatedがつけられたので、
コンストラクターではなく、valueOfを使用するのが良いです。

Doubleのソースコード

Floatのソースコード

Booleanについて

Booleanでは、フィールドで定義された定数を返しています。

Byteについて

Byteでは、常にキャッシュから値を取得して返すようになっています。
バイトの値の範囲は、-128から127なので、全ての値がキャッシュされています。

Shortについて

ShortもLongのように、-128から127の範囲でキャッシュされています。

Characterについて

Characterでは、\u0000から\u007Fまでの値がキャッシュされています。
この範囲には、制御文字とよく使う記号や英数字が含まれています。
Javadocには、この範囲外の値をキャッシュすることもあると記載されていますが、
ソースコードを見る限り、そのようなことはなさそうです。

Stringについて

Stringはラッパークラスではないですが、よく使うので簡単に触れておきます。

Stringは、オブジェクトがJVM内部のConstant Poolに保存されていて、
同じ文字列のオブジェクトがあった場合は、
Constant Poolのオブジェクトへの参照を返すようになっています。

Stringのinternメソッドを実行すると、
明示的にConstant Poolのオブジェクトへの参照を取得できます。
internはnativeメソッドです。

ちなみに、以下の例の2つ目のように、newを使ってStringを生成した場合には、
Constant Poolからは取得されません。

まとめ

今回はラッパークラスのキャッシュについてまとめました。
よく使う値などをキャッシュしておくことで、オブジェクト生成を減らすという考えは、
デザインパターンのFlyweightパターンに似ています。

Flyweightパターンのように生成したオブジェクトを全てキャッシュせずに、
よく使われる一部のオブジェクトだけをキャッシュしておきたい場合は、
ラッパークラスのキャッシュの実装を参考にするのも良いかもしれません。