コンストラクタを呼ばないでインスタンスを生成してリフレクションで横から強制的に初期化という行為が必要だった。sun.misc.UnsafeというAPIを使用するとできるらしいのでやってみた。あっけなく出来てしまった。まずは今回テストしたコードを下記に貼り付けます。
import java.lang.reflect.Field;
import sun.misc.Unsafe;
public class UnsafeTest {
public UnsafeTest() {
throw new RuntimeException("can't instantiate this class");
}
private String name = "";
public void hello() {
System.out.println("hello, " + name);
}
public static void main(String[] args) {
Unsafe unsafe = getUnsafe();
try {
while (true) {
UnsafeTest instance = (UnsafeTest) unsafe.allocateInstance(UnsafeTest.class);
instance.name = "hoge";
instance.hello();
}
} catch (InstantiationException e) {
e.printStackTrace();
}
}
private static Unsafe getUnsafe() {
try {
Field singleoneInstanceField = Unsafe.class.getDeclaredField("theUnsafe");
singleoneInstanceField.setAccessible(true);
return (Unsafe) singleoneInstanceField.get(null);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
実行すると延々と、"hello, hoge"と出力されます。
このクラスはコンストラクタを呼ぶと例外を飛ばす酷いやつですが、ちゃんとインスタンス化できていることがわかります。
コードUnsafeインスタンスの取得がリフレクション経由になっていますが、どうやら、この取り方が自分が調べた限り主流のようです。
理由は
- Unsafeオブジェクトはprivateコンストラクタなのでインスタンス化できない
- Unsafe.getUnsafe()メソッドはセキュリティ制御されていて、trustedなクラスからしか使用できない
- けど、そいつのインスタンスはUnsafeクラスのstaticインスタンス theUnsafe に格納されているから、リフレクションで「盗む」ことができる
ということみたいです。
詳しくは http://mishadoff.github.io/blog/java-magic-part-4-sun-dot-misc-dot-unsafe/
無限ループにしたのは、Unsafeという名前にビビったので、まさかメモリ管理されないとかないか、調べるためです。
実行させたまま、jvisualvmででヒープ情報を見たところ右肩上がりにはなっていないことがわかります。
結果は次の通り
なお、この結果は環境に依存するものですので、他の方の環境では同じ結果になるとは限りませんのでご了承下さい。
Java7
Java8
Java5
念の為に、heap dumpも取っていましたがUnsafeTestのインスタンス数は0となっていましたので、確かにGCされているようです。