私は、最小限のやや-怠惰(書いたint
)シーケンスクラス、GarbageTest.javaを私は、Javaで、非常に長いClojureの中に私ができる方法を怠惰なシーケンスを処理できるかどうかを確認するために、実験として、。
与えられたnaturals()
怠惰な、無限、自然数の列を返すメソッド。drop(n,sequence)
最初のドロップ方法n
の要素sequence
の残りを返しますsequence
。そしてnth(n,sequence)
この方法は、リターンは単にこと:drop(n, lazySeq).head()
、私は2つのテストを書きました:
static int N = (int)1e6;
// succeeds @ N = (int)1e8 with java -Xmx10m
@Test
public void dropTest() {
assertThat( drop(N, naturals()).head(), is(N+1));
}
// fails with OutOfMemoryError @ N = (int)1e6 with java -Xmx10m
@Test
public void nthTest() {
assertThat( nth(N, naturals()), is(N+1));
}
なお、本体dropTest()
の本体をコピーすることによって生成されたnthTest()
のIntelliJの「インライン」にリファクタリングを呼び出す当時とnth(N, naturals())
コールを。だから、の動作はと私には思えるdropTest()
の動作と同じでなければなりませんnthTest()
。
しかし、それは同じではありません!dropTest()
一方、1E8にNで完全に実行され、最大nthTest()
で失敗OutOfMemoryError
1E6として小さなとしてNため。
私は内部クラスを避けてきました。そして私は私のコードの変形、を試してきましたClearingArgsGarbageTest.java他のメソッドを呼び出す前に、メソッドのパラメータをゼロにします、。私はYourKitプロファイラを適用しました。私は、バイトコードを見てきました。私はちょうど原因リーク見つけることができないnthTest()
失敗します。
「漏れる」どこにありますか?そして、なぜないnthTest()
ながらリークを持っていdropTest()
ないのですか?
ここからのコードの残りの部分だGarbageTest.javaあなたはGitHubのプロジェクトにクリックスルーしたくない場合は:
/**
* a not-perfectly-lazy lazy sequence of ints. see LazierGarbageTest for a lazier one
*/
static class LazyishSeq {
final int head;
volatile Supplier<LazyishSeq> tailThunk;
LazyishSeq tailValue;
LazyishSeq(final int head, final Supplier<LazyishSeq> tailThunk) {
this.head = head;
this.tailThunk = tailThunk;
tailValue = null;
}
int head() {
return head;
}
LazyishSeq tail() {
if (null != tailThunk)
synchronized(this) {
if (null != tailThunk) {
tailValue = tailThunk.get();
tailThunk = null;
}
}
return tailValue;
}
}
static class Incrementing implements Supplier<LazyishSeq> {
final int seed;
private Incrementing(final int seed) { this.seed = seed;}
public static LazyishSeq createSequence(final int n) {
return new LazyishSeq( n, new Incrementing(n+1));
}
@Override
public LazyishSeq get() {
return createSequence(seed);
}
}
static LazyishSeq naturals() {
return Incrementing.createSequence(1);
}
static LazyishSeq drop(
final int n,
final LazyishSeq lazySeqArg) {
LazyishSeq lazySeq = lazySeqArg;
for( int i = n; i > 0 && null != lazySeq; i -= 1) {
lazySeq = lazySeq.tail();
}
return lazySeq;
}
static int nth(final int n, final LazyishSeq lazySeq) {
return drop(n, lazySeq).head();
}
あなたの方法で
static int nth(final int n, final LazyishSeq lazySeq) {
return drop(n, lazySeq).head();
}
パラメータの変数はlazySeq
全体の中にあなたのシーケンスの最初の要素への参照を保持するdrop
操作。これは、ガベージコレクションを取得してから、シーケンス全体を防ぐことができます。
とは対照的に
public void dropTest() {
assertThat( drop(N, naturals()).head(), is(N+1));
}
あなたのシーケンスの最初の要素はで返されるnaturals()
と、直接の呼び出しに渡されたdrop
ので、オペランドスタックから削除、および実行中に存在していませんdrop
。
パラメータ変数を設定するにはあなたの試みnull
、すなわち
static int nth(final int n, /*final*/ LazyishSeq lazySeqArg) {
final LazyishSeq lazySeqLocal = lazySeqArg;
lazySeqArg = null;
return drop(n,lazySeqLocal).head();
}
今、助けにはならない、lazySeqArg
変数があるnull
が、lazySeqLocal
最初の要素への参照を保持しています。
ローカル変数は、そうでなければ未使用のオブジェクトのコレクションであり、一般的にガベージコレクションを妨げない許可が、それは特定の実装では、それを行うことが可能であることを意味するものではありません。
HotSpot JVMの場合は、唯一の最適化されたコードは、このような未使用の参照を取り除くでしょう。しかし、ここで、nth
重いものを内で発生するとして、ホットスポットではないdrop
方法。
これは、同じ問題はで表示されない理由であるdrop
、それはまた、そのパラメータの変数の最初の要素への参照を保持しているにもかかわらず、方法。drop
この方法は、実際の仕事をしてループが含まれています、したがって、それは一連の処理済み部分が収集になることを可能にすること、未使用の変数を排除することがありJVMによって最適化され得ることは非常に可能性があります。
JVMの最適化に影響を与える可能性があり、多くの要因があります。コードの異なる形状のほかに、最適化されていない段階の間、その急速なメモリの割り当ても、オプティマイザの改良を減らすことができると思われます。私が実行したときに実際に、-Xcompile
完全に解釈実行を禁止する、両方の変異体が正常に実行、でもint N = (int)1e9
もう問題ありません。もちろん、コンパイルを強制的に起動時間を発生させます。
私は私がなぜ混合モードを実行理解していないことを認めざるを得ないというはるかに悪いと私はさらに調査します。しかし、一般的に、あなたは1つの環境に集めたオブジェクトは、別のメモリに滞在可能ので、ガベージコレクタの効率は実装依存であることを認識する必要があります。