sun.misc.Unsafe とは
sun.misc.Unsafe はネイティブメソッドを利用して Java のメモリモデルを書き換えるAPIを公開しています。
OpenJDKのJavaDocが以下で確認できます。
http://www.docjar.com/docs/api/sun/misc/Unsafe.html
sun.misc.Unsafe のインスタンスを取得する
Unsafe なので、簡単には利用できないようになっています。
コンストラクタはプライベートな上、getUnsafe()
では呼び出し元のクラスの getClassLoader()
が null
の場合(つまりブートストラップ・クラスローダ)からしかインスタンスを取得できないようになっています。
private static final Unsafe theUnsafe; private Unsafe() { } public static Unsafe getUnsafe() { Class var0 = Reflection.getCallerClass(); if(!VM.isSystemDomainLoader(var0.getClassLoader())) { throw new SecurityException("Unsafe"); } else { return theUnsafe; } }
しかし、セキュリティマネージャが許せば、以下のようにリフレクションでstaticなフィールドのインスタンスを無理やり取得することができます。
Field unsafeField = Unsafe.class.getDeclaredField("theUnsafe"); unsafeField.setAccessible(true); Unsafe unsafe = (Unsafe) unsafeField.get(null);
またはプライベートなコンストラクタを無理やり呼んでインスタンスを取得することもできます。
Constructor<Unsafe> unsafeConstructor = Unsafe.class.getDeclaredConstructor(); unsafeConstructor.setAccessible(true); Unsafe unsafe = unsafeConstructor.newInstance();
コンストラクタを呼ばずにオブジェクトを取得する
以下のようなクラスがあった場合、どうやっても例外となりインスタンスを得ることはできません。
public class Demo { public Demo() { throw new IllegalStateException(); } }
これは以下のようにリフレクション使っても当然例外になります。
Demo demo = Demo.class.newInstance();
こんな時 Unsafe を使うと、コンストラクタをバイパスしてインスタンス化できます。
Field unsafeField = Unsafe.class.getDeclaredField("theUnsafe"); unsafeField.setAccessible(true); Unsafe unsafe = (Unsafe) unsafeField.get(null); Demo demo = (Demo) unsafe.allocateInstance(Demo.class); assertThat(demo, is(notNullValue()));
static final なクラスフィールドを書き換える
final なフィールドでも、リフレクション使えば(セキュリティマネージャが許せば)書き換えてしまうことができます。 例えば以下のクラスの period フィールドを変更したい場合、
public class Demo { public final Period period; public Demo() { period = Period.ofDays(5); } }
以下のように簡単に書き換えてしまうことができます。
Demo demo = new Demo(); Field f = Demo.class.getDeclaredField("period"); f.setAccessible(true); f.set(demo, Period.ofDays(9)); assertThat(demo.period, is(Period.ofDays(9)));
しかし、以下のように static final な場合はリフレクションでは歯が立ちません。
public class Demo { public static final Period period = Period.ofDays(5); }
こんな時 Unsafe を使えば書き換えができてしまいます。
Demo demo = new Demo(); Field f = Demo.class.getDeclaredField("period"); unsafe.putObject(unsafe.staticFieldBase(f), unsafe.staticFieldOffset(f), Period.ofDays(9)); assertThat(demo.period, is(Period.ofDays(9)));
リフレクションでもできますが、static ではないフィールドの書き換えは以下のようになります。
Field f = Demo.class.getDeclaredField("period"); unsafe.putObject(demo, unsafe.objectFieldOffset(f), Period.ofDays(9));
チェック例外を throw する
リフレクションを使ってインスタンスを生成する場合、Class.newInstance()
を使いますが、対象のコンストラクタがチェック例外を投げる場合があります。
しかし newInstance()
は未知のコンストラクタのチェック例外を他の例外でラップすることなくスローしてきます。
こんなことがなぜ可能かというと、Unsafe が登場しまして、以下のように処理されています。
try { return tmpConstructor.newInstance((Object[])null); } catch (InvocationTargetException e) { Unsafe.getUnsafe().throwException(e.getTargetException()); // Not reached return null; }
つまり、以下のように IOException などのチェック例外を、throws 句を使わずにスローすることができます。
public void throwException() { unsafe().throwException(new IOException()); }
他にもいろいろ
メモリを確保したり、確保したメモリに書き込んだり、開放したり。
public native long allocateMemory(long bytes); public void copyMemory(long srcAddress, long destAddress, long bytes); public native void freeMemory(long address);
クラスのバイト配列からクラス定義したり、
public native Class defineClass(String name, byte[] b, int off, int len);
ロードアベレージ直接取得したり、
public native int getLoadAverage(double[] loadavg, int nelems)
オブジェクトのモニタロックを操作したり、
public native void monitorEnter(Object o); public native void monitorExit(Object o);
色々あります。