Java 9のHachim IntegerCache

多くの人にとって、Java 9への切り替えは抽象的なもののように見えます。 Peter Vargasが彼の記事[1]で引用した1つの短い勝利例で、これを実用的な平面に翻訳しましょう。



これはギャグ付きの「誤った翻訳」というジャンルの記事です。私が見ているように、私はアーティストだからです=)ソースへのリンク-いつものように、テキストの下部。



5年前、ピーターはJDKでのIntegerCacheのハッキングに関するハンガリー語のブログ投稿を公開しました。 これは、実行時のほんの小さな実験であり、学習の増加、反射の仕組み、整数クラスの仕組みの理解以外の実用的なアプリケーションはありません。



実際の乱数の生成は、システムのエントロピーに依存します[2] 。 これは正直なサイコロで行うことができると主張する人もいます[3]



画像






他の人は、java.math.Random.nextInt()メソッドの本体を再定義することが私たちの助けになると信じています。



古代のボタンアコーディオンを知らない人のために[4] 。 2013年のオランダJPointのハッカソンは、OpenJDKのアセンブリと変更について議論しました。 Roy van RijnがWindowsでそれを構築することを学んだ後(2017年に私がここに書いた方法[5] )、彼はすぐに仕事に取りかかり、彼の最初のコミットをしました。



OpenJDKのコアを変更する代わりに(すべてネイティブコードであるため、このための理学博士である必要があります)、彼はベースライブラリが単なるJavaクラスであり、カリスマ性に対して無防備であることを発見しました。 [openjdk] / jdk / src / share / classesを見ると 、「java。*」、「Javax。*」、さらには「sun。*」などの通常のパッケージディレクトリを見つけることができます。 したがって、ダーティブートで[openjdk] /jdk/src/share/classes/java/util/Random.javaにアクセスし、明らかな変更を加えることができます。



public int nextInt() { return 14; }
      
      





JDKの再構築後、すべての新しいRandom()への呼び出し。NextInt()は実際に14を返します。



しかし、これはすべて完全なゴミです。 本当の男の子は、エントロピーを追加する実際の方法は、JVMの開始時にjava.lang.Integer.IntegerCacheを書き換えることであることを知っています (そして、以下にその方法を示します)。



Integerには、-128〜127の範囲のInteger型のオブジェクトを含むプライベート内部クラスIntegerCacheが含まれることを思い出してください。コードがIntegerで囲まれ、この範囲の値を持つ場合、ランタイムは新しいIntegerを作成する代わりにキャッシュを使用します。 これはすべて速度の最適化のためであり、実際のプログラムでは数値が常にこの範囲に収まると仮定しています(少なくとも配列のインデックスを作成します)。



この副作用は、数値が指定された範囲内にある間に比較演算子を使用してint値を比較できるという周知の事実です。 このようなコード(誤って記述されている)が通常、あらゆる種類のユニットテスト(一貫性を保つために誤って記述されている)で動作するのは面白いですが、値が128を超えるとすぐに実際に使用されてしまいます。このhabropostの著者は、この実装の詳細神は光に引き込まれ、インタビューのためのテストに落ち着き、多くの善良な人々の強化されていない子供の精神をしっかりと台無しにしました。



警告、危険。 IntegerCacheをリフレクションにフックすると、魔法の副作用が発生する可能性があり、特定の場所だけでなく、このJVMのコンテンツ全体にも影響を及ぼします。 つまり、サーブレットがキャッシュの一部を変更すると、同じTomcat内の他のすべてのサーブレットに問題が生じます。 オルソ、警告した。



それでは、Java 9ベータ版を使用して、Java 8で転がしたのと同じがらくたにしてみましょう。Lucas [2]の記事からコードをコピーしましょう。



 import java.lang.reflect.Field; import java.util.Random; public class Entropy { public static void main(String[] args) throws Exception { //  IntegerCache  reflection Class<?> clazz = Class.forName( "java.lang.Integer$IntegerCache"); Field field = clazz.getDeclaredField("cache"); field.setAccessible(true); Integer[] cache = (Integer[]) field.get(clazz); //  Integer cache for (int i = 0; i < cache.length; i++) { cache[i] = new Integer( new Random().nextInt(cache.length)); } //  ! for (int i = 0; i < 10; i++) { System.out.println((Integer) i); } } }
      
      





約束どおり、このコードはリフレクションを使用してIntegerCacheにアクセスし、ランダムな値を設定します。 なんて素晴らしい汚い決断だ!



次に、Nineの下で同じコードを実行します。 汚い男の子にとっては悪いニュースだ。休日はないだろう。 彼女を屈辱しようとすると、ナインはもっと真剣に反応します:



 Exception in thread "main" java.lang.reflect.InaccessibleObjectException: Unable to make field static final java.lang.Integer[] java.lang.Integer$IntegerCache.cache accessible: module java.base does not "opens java.lang" to unnamed module @1bc6a36e
      
      





エイトには存在しなかった例外がありました。 JDKランタイムの一部であり、javaプログラムによって自動的にインポートされるjava.baseモジュールは、名前のないモジュールに必要なモジュールを「オープン」(sic)しないため、オブジェクトは利用できないと表示されます。 エラーは、フィールドにアクセスできるようにする行にあります。



Eightで簡単に到達できるオブジェクトは、モジュールシステムによって保護されているため使用できなくなりました。 コードがリフレクションを使用してフィールド、メソッドなどにアクセスできるのは、クラスが同じモジュールにある場合、またはこのモジュールが全世界または特定のモジュールのリフレクションによるアクセスにアクセスできる場合のみです。



これは、次のようなmodule-info.javaというファイルで行われます。



 module randomModule { exports ru.habrahabr.module.random; opens ru.habrahabr.module.random; }
      
      





java.baseモジュールはアクセスを許可しないため、足を吸います。 より美しいエラーを見たい場合は、コード用のモジュールを作成し、エラーテキストでその名前を確認できます。



プログラムでアクセスを開くことはできますか? java.lang.reflect.ModuleにはaddOpensメソッドがいくつかありますが、動作しますか? 悪いニュースはありません。 モジュールBのモジュールAでパッケージを開くことができるのは、このメソッドを呼び出すモジュールCのパッケージが既に開いている場合のみです。 したがって、モジュールは、既に持っているが、開くことはできない、閉じている権利を相互に転送できます。



しかし、これは良いニュースと考えることもできます。 Javaはそれ以上に成長していますが、NineはEightほど簡単に壊れません。 少なくともこの小さな穴は閉じられました。 Javaはおもちゃではなく、ますます専門的なツールになりつつあります。 すぐに、IBM RPGとCOBOLで作成されたすべての重要なソフトウェアを書き換えることができるようになります。



ああ、それはまだこのように壊れている可能性があります:



 public class IntegerHack { public static void main(String[] args) throws Exception { //  IntegerCache  reflection Class usf = Class.forName("sun.misc.Unsafe"); Field unsafeField = usf.getDeclaredField("theUnsafe"); unsafeField.setAccessible(true); sun.misc.Unsafe unsafe = (sun.misc.Unsafe)unsafeField.get(null); Class<?> clazz = Class.forName("java.lang.Integer$IntegerCache"); Field field = clazz.getDeclaredField("cache"); Integer[] cache = (Integer[])unsafe.getObject(unsafe.staticFieldBase(field), unsafe.staticFieldOffset(field)); //  Integer cache for (int i = 0; i < cache.length; i++) { cache[i] = new Integer( new Random().nextInt(cache.length)); } //  ! for (int i = 0; i < 10; i++) { System.out.println((Integer) i); } } }
      
      





Unsafeも禁止すべきでしょうか?



ところで、ここにコメントを書くのが怖いなら、あなたは私のfbに忍び寄るか、いくつかのジョーカー2017でライブに会うか、ノボシビルスクのクロノスやグースのビジネスセンターの横を渡って、スムージーでビールを持って、他の面白いゲームについて話し合うことができます。 ゲームの神にもっとゲームを!



PS私は記事に猫を挿入するように頼まれました。 したがって、ここにマーク・ラインホールドの笑顔の珍しい写真があります:







ソース



[1] オリジナル記事

[2] ハンガリー語の記事からコードを復活させた男

[3] 乱数に関するよく知られた図

[4] nextIntをオーバーライドする方法

[5] WindowsでJavaを構築する方法



All Articles