GMOアドマーケティングのT.Nです。
今年も弊社にエンジニアの新入社員が入ってきました。
新入社員研修の一つとして、Java研修も実施しました。
Java研修では、「Java本格入門」という本でJavaの基礎を学びながら、
Servlet/JSPでWebアプリケーションの開発を行いました。
研修を進めていく中で、本に記載されている以下の内容について、
なぜ効率に違いがあるのかという質問を受けました。
1 2 3 |
// 以下のソースコードは「Java本格入門」の69ページから引用 Integer num01 = new Integer(10); // 新たなオブジェクトを生成するので効率が悪い Integer num02 = Integer.valueOf(10); // キャッシュされたオブジェクトを返す |
引用元: 技術評論社「Java本格入門」69ページ
これは、valueOfを実行した場合は、
内部でキャッシュしたデータを返しているためなのですが、
その仕組みが気になったので、
今回は、Integerクラスのキャッシュと、
その他のラッパークラスのキャッシュについて調べてまとめました。
ちなみに、今回はJDK 12のソースコードを調べました。
JDK 11からJDK 12で、Cacheに関係する内部クラスの処理に変更がありました。
例えば、Integerの場合は、
IntegerCacheというクラスでキャッシュしているのですが、
IntegerCacheの初期化時に、以下のような処理が追加されています。
1 2 |
// Load IntegerCache.archivedCache from archive, if possible VM.initializeFromArchive(IntegerCache.class); |
これは、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の実装は以下のようになっています。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 |
@HotSpotIntrinsicCandidate public static Integer valueOf(int i) { if (i >= IntegerCache.low && i <= IntegerCache.high) return IntegerCache.cache[i + (-IntegerCache.low)]; return new Integer(i); } private static class IntegerCache { static final int low = -128; static final int high; static final Integer[] cache; static Integer[] archivedCache; static { // high value may be configured by property int h = 127; String integerCacheHighPropValue = VM.getSavedProperty("java.lang.Integer.IntegerCache.high"); if (integerCacheHighPropValue != null) { try { h = Math.max(parseInt(integerCacheHighPropValue), 127); // Maximum array size is Integer.MAX_VALUE h = Math.min(h, Integer.MAX_VALUE - (-low) -1); } catch( NumberFormatException nfe) { // If the property cannot be parsed into an int, ignore it. } } high = h; // Load IntegerCache.archivedCache from archive, if possible VM.initializeFromArchive(IntegerCache.class); int size = (high - low) + 1; // Use the archived cache if it exists and is large enough if (archivedCache == null || size > archivedCache.length) { Integer[] c = new Integer[size]; int j = low; for(int i = 0; i < c.length; i++) { c[i] = new Integer(j++); } archivedCache = c; } cache = archivedCache; // range [-128, 127] must be interned (JLS7 5.1.7) assert IntegerCache.high >= 127; } private IntegerCache() {} } |
引数の値が-128から127の場合は、キャッシュの値を使用し、
それ以外の場合は、新しくIntegerオブジェクトを生成しています。
-128から127の範囲外の場合はどちらを使用しても良さそうですが、
Java 9からIntegerのコンストラクターに@Deprecatedがつけられたので、
範囲外の場合もvalueOfを使うのが良いです。
1 2 3 4 |
@Deprecated(since="9") public Integer(int value) { this.value = value; } |
ちなみに、-128から127という範囲は、
最大値のみ、以下のようにVMオプションで値を指定することで変更可能です。
1 |
-XX:AutoBoxCacheMax=254 |
Longについて
LongでもIntegerと同じように内部でオブジェクトをキャッシュしています。
範囲は-128から127で固定されていて、Integerのように最大値を指定することはできません。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
@HotSpotIntrinsicCandidate public static Long valueOf(long l) { final int offset = 128; if (l >= -128 && l <= 127) { // will cache return LongCache.cache[(int)l + offset]; } return new Long(l); } private static class LongCache { private LongCache() {} static final Long[] cache; static Long[] archivedCache; static { int size = -(-128) + 127 + 1; // Load and use the archived cache if it exists VM.initializeFromArchive(LongCache.class); if (archivedCache == null || archivedCache.length != size) { Long[] c = new Long[size]; long value = -128; for(int i = 0; i < size; i++) { c[i] = new Long(value++); } archivedCache = c; } cache = archivedCache; } } |
ちなみに、JDK 11では、以下のようなシンプルな処理になっています。
1 2 3 4 5 6 7 8 9 10 |
private static class LongCache { private LongCache(){} static final Long cache[] = new Long[-(-128) + 127 + 1]; static { for(int i = 0; i < cache.length; i++) cache[i] = new Long(i - 128); } } |
DoubleとFloatについて
DoubleとFloatでは、
Javadocに、頻繁に要求される値はキャッシュされるというような記載がありますが、
実際はキャッシュされておらず、
valueOfの中で新しいインスタンスを生成して返しています。
Integerなどの他のラッパークラスと同じように、
Java 9からコンストラクターに@Deprecatedがつけられたので、
コンストラクターではなく、valueOfを使用するのが良いです。
Doubleのソースコード
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
/** * Returns a {@code Double} instance representing the specified * {@code double} value. * If a new {@code Double} instance is not required, this method * should generally be used in preference to the constructor * {@link #Double(double)}, as this method is likely to yield * significantly better space and time performance by caching * frequently requested values. * * @param d a double value. * @return a {@code Double} instance representing {@code d}. * @since 1.5 */ @HotSpotIntrinsicCandidate public static Double valueOf(double d) { return new Double(d); } |
Floatのソースコード
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
/** * Returns a {@code Float} instance representing the specified * {@code float} value. * If a new {@code Float} instance is not required, this method * should generally be used in preference to the constructor * {@link #Float(float)}, as this method is likely to yield * significantly better space and time performance by caching * frequently requested values. * * @param f a float value. * @return a {@code Float} instance representing {@code f}. * @since 1.5 */ @HotSpotIntrinsicCandidate public static Float valueOf(float f) { return new Float(f); } |
Booleanについて
Booleanでは、フィールドで定義された定数を返しています。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
/** * The {@code Boolean} object corresponding to the primitive * value {@code true}. */ public static final Boolean TRUE = new Boolean(true); /** * The {@code Boolean} object corresponding to the primitive * value {@code false}. */ public static final Boolean FALSE = new Boolean(false); @HotSpotIntrinsicCandidate public static Boolean valueOf(boolean b) { return (b ? TRUE : FALSE); } |
Byteについて
Byteでは、常にキャッシュから値を取得して返すようになっています。
バイトの値の範囲は、-128から127なので、全ての値がキャッシュされています。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
@HotSpotIntrinsicCandidate public static Byte valueOf(byte b) { final int offset = 128; return ByteCache.cache[(int)b + offset]; } private static class ByteCache { private ByteCache() {} static final Byte[] cache; static Byte[] archivedCache; static { final int size = -(-128) + 127 + 1; // Load and use the archived cache if it exists VM.initializeFromArchive(ByteCache.class); if (archivedCache == null || archivedCache.length != size) { Byte[] c = new Byte[size]; byte value = (byte)-128; for(int i = 0; i < size; i++) { c[i] = new Byte(value++); } archivedCache = c; } cache = archivedCache; } } |
Shortについて
ShortもLongのように、-128から127の範囲でキャッシュされています。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
@HotSpotIntrinsicCandidate public static Short valueOf(short s) { final int offset = 128; int sAsInt = s; if (sAsInt >= -128 && sAsInt <= 127) { // must cache return ShortCache.cache[sAsInt + offset]; } return new Short(s); } private static class ShortCache { private ShortCache() {} static final Short[] cache; static Short[] archivedCache; static { int size = -(-128) + 127 + 1; // Load and use the archived cache if it exists VM.initializeFromArchive(ShortCache.class); if (archivedCache == null || archivedCache.length != size) { Short[] c = new Short[size]; short value = -128; for(int i = 0; i < size; i++) { c[i] = new Short(value++); } archivedCache = c; } cache = archivedCache; } } |
Characterについて
Characterでは、\u0000から\u007Fまでの値がキャッシュされています。
この範囲には、制御文字とよく使う記号や英数字が含まれています。
Javadocには、この範囲外の値をキャッシュすることもあると記載されていますが、
ソースコードを見る限り、そのようなことはなさそうです。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 |
/** * Returns a {@code Character} instance representing the specified * {@code char} value. * If a new {@code Character} instance is not required, this method * should generally be used in preference to the constructor * {@link #Character(char)}, as this method is likely to yield * significantly better space and time performance by caching * frequently requested values. * * This method will always cache values in the range {@code * '\u005Cu0000'} to {@code '\u005Cu007F'}, inclusive, and may * cache other values outside of this range. * * @param c a char value. * @return a {@code Character} instance representing {@code c}. * @since 1.5 */ @HotSpotIntrinsicCandidate public static Character valueOf(char c) { if (c <= 127) { // must cache return CharacterCache.cache[(int)c]; } return new Character(c); } private static class CharacterCache { private CharacterCache(){} static final Character[] cache; static Character[] archivedCache; static { int size = 127 + 1; // Load and use the archived cache if it exists VM.initializeFromArchive(CharacterCache.class); if (archivedCache == null || archivedCache.length != size) { Character[] c = new Character[size]; for (int i = 0; i < size; i++) { c[i] = new Character((char) i); } archivedCache = c; } cache = archivedCache; } } |
Stringについて
Stringはラッパークラスではないですが、よく使うので簡単に触れておきます。
Stringは、オブジェクトがJVM内部のConstant Poolに保存されていて、
同じ文字列のオブジェクトがあった場合は、
Constant Poolのオブジェクトへの参照を返すようになっています。
Stringのinternメソッドを実行すると、
明示的にConstant Poolのオブジェクトへの参照を取得できます。
internはnativeメソッドです。
1 |
public native String intern(); |
ちなみに、以下の例の2つ目のように、newを使ってStringを生成した場合には、
Constant Poolからは取得されません。
1 2 3 4 5 |
// Constant Poolから取得 String value1 = "GMOアドマーケティング"; // 新しくオブジェクトが生成される String value2 = new String("GMOアドマーケティング"); |
まとめ
今回はラッパークラスのキャッシュについてまとめました。
よく使う値などをキャッシュしておくことで、オブジェクト生成を減らすという考えは、
デザインパターンのFlyweightパターンに似ています。
Flyweightパターンのように生成したオブジェクトを全てキャッシュせずに、
よく使われる一部のオブジェクトだけをキャッシュしておきたい場合は、
ラッパークラスのキャッシュの実装を参考にするのも良いかもしれません。