怠zyな人だけがJavaのUnsafeについて知らなかったが、これはJavaプラットフォームの境界を消去し、パブリックAPIにマッピングされていないパスを開くSun / Oracle JDKの唯一の魔法のクラスではありません。 実際のプロジェクトで恩恵を受けたそれらのいくつかについてお話します。 ただし、文書化されていない機能は、アプリケーションから他のJavaプラットフォームへの移植性を奪い、さらに、重大なエラーの潜在的な原因になることを忘れないでください。 「アプリケーション」という言葉も無駄に書きました。 以下に説明するクラスは、アプリケーションにはまったく適していません。 むしろ、システムソフトウェアと好奇心programmer盛なプログラマー、つまり あなたのために:)
クリーナー
すべての優れたJavaプログラマーは、 ファイナライザーが悪であることを知っています。 しかし、この場合、適切なプログラマーとしてライブラリを使用している全員がclose()を呼び出すという保証がない場合、どのように置き換えることができますか? 結局のところ、JDK自体の内部で何が使用されているのでしょうか?
システムクラスでは、PhantomReferenceに基づくsun.misc.Cleanerが、ファイナライザーのより信頼性が高く軽量な代替として使用されます。 Cleanerが参照するオブジェクトがphantom-reachableになったことをガベージコレクターが検出するとすぐに、通常はリソースを解放するために、指定されたRunnableが起動されます。
public NeedsCleanup() { this.resource = unsafe.allocateMemory(16*1024*1024); Cleaner.create(this, new Destructor(resource)); } private static class Destructor implements Runnable { final long resource; Destructor(long resource) { this.resource = resource; } public void run() { unsafe.freeMemory(resource); } }
有用性にもかかわらず、このクラスはパブリックAPIの一部になることは想定されていません。誤用すると、参照ハンドラーストリームをブロックしたり、アプリケーションを停止したりする可能性があるためです。
MappedByteBufferのマッピング解除
そして、これはJDKシステムクラスのCleanerの単なる例です。
FileChannel.map()を介してJavaでメモリマップファイルを使用した人は、おそらくMappedByteBufferにunmap()メソッドがなかったことを何度も後悔していました。 その理由があります。
しかし、ことわざのように、もしできないが、本当にしたいのなら、できます。 MappedByteBufferの実装は、バッファが到達不能になったときに、ガベージコレクション後にマップ解除を実行するクリーナーを作成します。 そのため、このクリーナーは手動で呼び出すこともできます。
public static void unmap(MappedByteBuffer bb) { if (bb instanceof sun.nio.ch.DirectBuffer) { ((sun.nio.ch.DirectBuffer) bb).cleaner().clean(); } }
SharedSecrets
興味深い名前ですね。 sun.misc.SharedSecretsは、システムクラスのプライベートデータにアクセスするためのインターフェイスを提供するユーティリティクラスです。 そこにはあまり興味深いものはありませんが。 たとえば、ロードされたクラスの定数プールにアクセスできます...理由は聞かないでください:)
私が取得したものから-Java FileDescriptorオブジェクトからネイティブ(OSレベル)ファイル記述子を取得します。 これは次のように行われます。
public static int getNativeFD(FileDescriptor fd) { return SharedSecrets.getJavaIOFileDescriptorAccess().get(fd); }
または、Javaアプリケーションが実行されているコンソールのエンコーディングを確認する方法の別の例を次に示します。
if (System.console() != null) { System.out.println(SharedSecrets.getJavaIOAccess().charset()); }
シグナルハンドラー
しかし、Javaプログラムでは、たとえばSIGINTなどのPOSIXシグナルをキャッチして処理できることをご存知ですか?
はい、sun.misc.Signalおよびsun.misc.SignalHandlerクラスを使用します。
ただし、このトピックに関する記事は既にあるので、これについては触れません。
Magicaccessor
そして、最も魔法のクラスは、その名前が示すように、今日はsun.reflect.MagicAccessorImplです。 クラスから継承されると、クラスは奇跡的にクラスのすべてのプライベートフィールドとメソッドにアクセスする機会を得ます! 秘Theは、バイトコード検証では、MagicAccessorImpl下位クラスのアクセスチェックを単純に省略することです。
独自のクイックシリアル化メカニズムを実装するのに非常に役立ちました。 結局、シリアライザーは、プライベートクラスフィールドを含むすべてのクラスフィールドを読み書きできる必要があります。 従来、これは、かなり遅いReflectionまたはUnsafeを使用して実現されていました。 しかし、文字通り1つのマシン命令にコンパイルされるgetfield / putfieldバイトコードを介してフィールドに直接アクセスするよりも速い方法はありません。
// ----- A.java ----- class A { private int privateField = 5; } // ----- B.java ----- class B extends sun.reflect.MagicAccessorBridge { public static void main(String[] args) { A a = new A(); a.privateField = 10; System.out.println(a.privateField); } } // ----- MagicAccessorBridge.java ----- package sun.reflect; public class MagicAccessorBridge extends MagicAccessorImpl { // Since MagicAccessorImpl is package-private, we'll make a public bridge }
問題は1つだけです。JVMのみがマジックアクセサーを認識しますが、javacは認識しません。javacは不正アクセスを継続して誓います。 この例をテストするには、最初にクラスAでフィールド修飾子をpublicに変更し、すべてをコンパイルしてから、プライベート修飾子を返し、クラスAを個別に再コンパイルする必要があります。 もちろん、それは不便です。したがって、実際には、MagicAccessorImplからの継承は、クラスが動的に生成される場合にのみ意味を持ちます。 ところで、JDKトレジャリーには、この場合のツールがあります:sun.tools.asm.Assemblerおよびパッケージsun.tools.asm全体。
警告:Sun独自のAPI
上記のクラスのいずれか、または太陽からのその他のものでプログラムを希釈しようとするとすぐに*パッケージは、コンパイラから確実に警告を受け取ります。
警告:sun.misc.UnsafeはSun独自のAPIであり、将来のリリースで削除される可能性があります
これは、アノテーションによって抑制することはできません。
喜んだり動揺したりすることもありますが、JDK 7では最終的に迷惑な警告を隠すことが可能になりました。 しかし、再び、秘密鍵の助けを借りてのみ:)
メソッドまたはクラスに注釈を追加する
@SuppressWarnings("sunapi")
パラメーターを指定してjavacを実行します
javac -XDenableSunApiLintControl TestUnsafe.java
それだけです、実験への道は明確です!