序文
安全でないクラスは、JDKソースコードの複数のクラスで使用されます。このクラスは、JVMをバイパスするためのいくつかの根本的な機能を提供し、その実装により効率を改善できます。しかし、それは両刃の剣です。その名前が予言されているように、それは安全ではありません、そして、それが割り当てる記憶は手動で自由にする必要があります(GCによってリサイクルされません)。安全でないクラスは、JNIの特定の機能に代わる簡単な代替品を提供します。効率を確保しながら、物事を簡単にします。
このクラスは太陽の下でクラスに属します。* APIであり、J2SEの実際の部分ではないため、公式のドキュメントは見つからない場合があります。残念ながら、コードドキュメントも優れていません。
この記事は、主に次の記事の編集と翻訳に関するものです。
http://mishadoff.com/blog/java-magic-part-4-sun-dot-misc-dot-unsafe/
1.安全でないAPIのほとんどの方法は、主に次のカテゴリを含む105の方法で構成されるネイティブ実装です。
(1)関連する情報。主にいくつかの低レベルのメモリ情報を返します:addresssize()、pagesize()
(2)関連するオブジェクト。主にオブジェクトとそのドメイン操作方法を提供する方法:allocateInstance()、objectfieldoffset()
(3)クラス関連。主にクラスとその静的ドメイン操作方法を提供します:staticfieldoffset()、defineclass()、definnonymousclass()、ensureclassInitialized()
(4)関連する配列。配列操作方法:arraybaseOffset()、arrayindexscale()
(5)同期関連。主に低レベルの同期プリミティブ(CPUベースのCAS(Compart-and-Swap)Primitivesなど)を提供します:MonitorEnter()、tryMonitorenter()、Monitorexit()、CompareAndswapint()、putorderedint()
(6)メモリ関連。直接メモリアクセス方法(JVMヒープをバイパスし、ローカルメモリを直接操作):AllocateMemory()、Copymemory()、freememory()、getAddress()、getint()、putint()
2。安全でないクラスインスタンスを取得します
安全でないクラスデザインは、JVM信頼できるスタートアップクラスローダーにのみ提供され、典型的なシングルトンパターンクラスです。そのインスタンスの取得方法は次のとおりです。
public static unsafe getunsafe(){class cc = sun.reflect.reflection.getCallerClass(2); if(cc.getClassLoader()!= null)Throw new SecurityException( "Unsafe"); thunsafeを返します;}非スタートクラスローダーは、unsafe.getunsafe()メソッドを直接呼び出し、セキュリティエクセプトをスローします(特定の理由には、JVMクラスの親ローディングメカニズムが含まれます)。
2つの解決策があります。 1つは、JVMパラメーターXBootClassPathを介してスタートアップクラスとして使用するクラスを指定することです。もう1つの方法はJava反射です。
フィールドF = unsafe.class.getDeclaredfield( "theunsafe"); f.setAccessible(true); unsafe unsafe =(unsafe)f.get(null);
プライベートシングルトンインスタンスのためにTrueにアクセスできる残酷に設定し、フィールドのGETメソッドを介して安全でないオブジェクトキャストを直接取得します。 IDEでは、これらのメソッドはエラーとしてマークされ、次の設定で解決できます。
設定 - > Java->コンパイラ - >エラー/警告 - >非推奨および制限API->禁止リファレンス - >警告
3。「興味深い」安全でないクラスのアプリケーションシナリオ
(1)クラスの初期化方法をバイパスします。 AllocateInstance()メソッドは、オブジェクトのコンストラクター、セキュリティチェッカー、または公開されていないコンストラクターをバイパスする場合に非常に便利になります。
クラスA {private long a; //初期化されていない値public a(){this.a = 1; //初期化} public long a(){return this.a; }}以下は、構築方法、反射法、AllocateInstance()の比較です。
o1 = new a(); // constructoro1.a(); //印刷1 a2 = a.class.newinstance(); // Reflectiono2.a(); //印刷1 a3 =(a)unsafe.allocateinstance(a.class); // unsafeo3.a(); //印刷0
AllocateInstance()はコンストラクター法にまったく入力しません。シングルトンモードでは、危機が見られるようです。
(2)メモリの変更
メモリの変更は、C言語で比較的一般的です。 Javaでは、セキュリティチェッカーをバイパスするために使用できます。
次の簡単なアクセスチェックルールを検討してください。
クラスガード{private int access_allowed = 1; public boolean giveaccess(){return 42 == access_allowed; }}通常の状況では、giveaccessは常に虚偽を返しますが、常に発生するとは限りません
ガードガード= new Guard(); Guard.GiveAccess(); // false、アクセスなし//メモリ汚職Guard.GiveAccess(); // true、アクセス許可
メモリオフセットを計算し、Putint()メソッドを使用することにより、クラスのアクセス_Allowedが変更されます。クラス構造がわかっている場合、データオフセットは常に計算できます(C ++のクラスのデータオフセット計算と一致)。
(3)C言語と同様のsizeof()関数を実装します
Java ReflectionとObjectFieldOffset()関数を組み合わせて、c like sizeof()関数を実装します。
public static long size of(object o){unsafe u = getunsafe();ハッシュセットフィールド= new Hashset();クラスc = o.getClass(); while(c!= object.class){for(field f:c.getdeclaredfields()){if((f.getModifiers()&modifier.static)== 0){fields.add(f); }} c = c.getsuperclass(); } // Offset long maxsize = 0を取得します。 for(field f:fields){long offset = u.objectfieldoffset(f); if(offset> maxsize){maxsize = offset; }} return((maxsize/8) + 1) * 8; //パディング}アルゴリズムのアイデアは非常に明確です。基礎となるサブクラスから始めて、それ自体の非静的なドメインを取り出し、すべてのスーパークラスを順番に取り出し、繰り返し計算をハッシュセットに配置します(Javaは単一の継承です)。
32ビットJVMでは、クラスファイルのオフセットが12の長い読み取りでサイズを取得できます。
public static long size of(object object){return getunsafe()。getAddress(remorize(getunsafe()。getint(object、4l)) + 12l);}remormize()関数は、署名されたintを符号なしに変換する方法です
private static long remormize(int value){if(value> = 0)return value; return(0l >>> 32)&value;}計算された2つのsizeof()のサイズは同じです。最も標準的なsizeof()実装は、java.lang.instrumentを使用することですが、コマンドラインパラメーター-javaagentを指定する必要があります。
(4)浅いJavaレプリケーションの実装
標準的な浅い複製スキームは、クローン可能なインターフェイスまたはそれ自体で実装された複製関数を実装することであり、それらは多目的関数ではありません。 sizeof()メソッドを組み合わせることで、浅いコピーを実現できます。
静的オブジェクトshallowcopy(object obj){long size = sizeof(obj); long start = toaddress(obj); long address = getunsafe()。allocatememory(size); getunsafe()。copymemory(start、address、size); Address(アドレス);}を返す次のToAddress()およびfromAddress()は、オブジェクトをそれぞれそのアドレスと逆操作に変換します。
static long toaddress(object obj){object [] array = new object [] {obj}; long baseOffset = getUnsafe()。arraybaseOffset(object []。class); return remormize(getunsafe()。getint(array、baseoffset));} static object fromdress(long address){object [] array = new object [] {null}; long baseOffset = getUnsafe()。arraybaseOffset(object []。class); getunsafe()。putlong(array、baseoffset、address); return array [0];}上記の浅いコピー機能は任意のJavaオブジェクトに適用でき、そのサイズは動的に計算されます。
(5)メモリ内のパスワードを排除します
パスワードフィールドは文字列に保存されますが、文字列リサイクルはJVMによって管理されます。最も安全な方法は、パスワードフィールドが使用された後に上書きすることです。
Field StringValue = string.class.getDeclaredfield( "value"); stringvalue.setaccesible(true); char [] mem =(char [])stringvalue.get(password);
(6)クラスの動的読み込み
クラスを動的にロードする標準的な方法はclass.forname()です(JDBCプログラムを書くとき、私はそれを深く覚えています)。 Unsafeは、Javaクラスファイルを動的にロードすることもできます。
byte [] classContents = getClassContent(); class c = getunsafe()。defineclass(null、classcontents、0、classcontents.length); c.getmethod( "a")。invoke(c.newinstance()、null); // 1getClassContent()メソッドは、クラスファイルをバイト配列に読み取ります。 private static byte [] getClassContent()Throws Exception {file f = new file( "/home/mishadoff/tmp/a.class"); fileInputStream input = new FileInputStream(f); byte [] content = new byte [(int)f.length()]; input.read(content); input.close();コンテンツを返す;}動的荷重、プロキシ、スライス、その他の機能に適用できます。
(7)パッケージ検出例外はランタイム例外です。
getunsafe()。throwexception(new ioException());
これは、チェックされた例外をキャッチしたくない場合に実行できます(推奨されません)。
(8)クイックシリアル化
標準のJava Serializableは非常に遅いため、クラスには公開パラメーターのないコンストラクターが必要であることも制限されています。外部化可能な方が優れているため、クラスがシリアル化されるスキーマを指定する必要があります。 Kryoなどの人気のある効率的なシリアル化ライブラリは、サードパーティライブラリに依存することで、メモリの消費を増加させます。 getint()、getlong()、getObject()、およびその他のメソッドを介してクラス内のドメインの実際の値を取得し、クラス名などの情報をファイルに一緒に持続できます。 Kryoは安全でないものを使用しようとしましたが、特定のパフォーマンス改善データはありません。 (http://code.google.com/p/kryo/issues/detail?id=75)
(9)非ジャバヒープにメモリを割り当てます
Javaを使用する新しいものは、ヒープ内のオブジェクトにメモリを割り当て、オブジェクトのライフサイクルはJVM GCによって管理されます。
クラスSuperArray {private final static int byte = 1;プライベートロングサイズ。プライベートロングアドレス; public superArray(long size){this.size = size; address = getunsafe()。allocatememory(size * byte); } public void set(long i、byte value){getunsafe()。putbyte(address + i * byte、value); } public int get(long idx){return getunsafe()。getbyte(address + idx * byte); } public long size(){return size; }}Unsafeによって割り当てられたメモリは、integer.max_valueによって制限されず、非ヒープメモリに割り当てられます。使用する場合、非常に注意する必要があります。手動でリサイクルするのを忘れた場合、メモリリークが発生します。違法な住所アクセスの場合、JVMがクラッシュします。大規模な連続領域、リアルタイムプログラミング(JVMレイテンシを許容しない)を割り当てる必要がある場合に使用できます。 Java.nioはこのテクノロジーを使用しています。
(10)Javaの並行性のアプリケーション
Unsafe.comPareandSwap()を使用することにより、効率的なロックフリーデータ構造を実装するために使用できます。
クラスカスカウンターはカウンター{プライベート揮発性長いカウンター= 0;プライベートの安全でない安全でない;プライベートロングオフセット。 public cascounter()throws Exception {unsafe = getunsafe(); offset = unsafe.objectfieldoffset(cascounter.class.getDeclaredfield( "counter")); } @Override public void increment(){old before = counter; while(!unsafe.compareandswaplong(これ、オフセット、前、 + 1の前)){before = counter; }} @Override public long getCounter(){return counter; }}テストを通じて、上記のデータ構造は基本的にJava原子変数の効率と同じです。 Java Atomic変数はUnsafeのCompareAndSwap()メソッドも使用し、この方法は最終的にCPUの対応するプリミティブに対応するため、非常に効率的です。ロックフリーハッシュマップ(http://www.azulsystems.com/about_us/presentations/lock-free-hashを実装するためのソリューションがあります。このソリューションのアイデアは、次のことです。各状態を分析し、コピーを作成し、コピーを変更し、CASプリミティブを使用し、スピンロック)。通常のサーバーマシン(CORE <32)では、CONCURRENTHASHMAP(JDK8の前に、デフォルトの16チャンネル分離ロックが実装され、CONCURRENTHASHMAPがロックフリーを使用して実装されています)は明らかに十分です。
要約します
上記は、この記事のコンテンツ全体です。この記事の内容には、すべての人の研究や仕事に特定の参照値があることを願っています。ご質問がある場合は、メッセージを残してコミュニケーションをとることができます。 wulin.comへのご支援ありがとうございます。