私は、Javaプログラムでいくつかの奇妙な行動を見ていて、動作が期待されている場合、私は思ったんだけど、それがどこかに文書だ場合。
私はいくつか置いていますWeakReference
コレクションにオブジェクトを。(はい、私は私が使用する必要があります知っているWeakHashMap
-それは同じ奇妙な行動を持っており、それがこの質問が何であるかではありません。)
いくつかの状況で、最後が参照するオブジェクトは、WeakReference
私はそれを期待していたときに収集したゴミを取得していないコレクションに入れました。
以下は、私が見ている挙動を示すユニットテストのコレクションがあります。これらのテストのすべてが書かれたとして渡し、奇数行動が見られるコメントがあります。(OracleのJDK 1.8とOpenJDKの11を使用してテスト済み)
最初のテストでは、私は、コレクションAに挿入していますWeakReference
関数呼び出しから返されたオブジェクトに:
List<WeakReference<Person>> refs = Lists.newArrayList();
refs.add(new WeakReference(getPerson("abc")));
期待通りに収集したすべてのgetごみを参照されるオブジェクト。
第二の試験では、私は、関数の返されたオブジェクトを保持するスコープの変数を作成する必要があり、作成するWeakReference
ことにし、コレクションにそれを挿入します。変数は、それはすべての参照を削除する必要がありますように思える範囲の外に出ます。すべてしかし最後のケースでは、これが本当である:それらが参照するオブジェクトは、ごみを収集します。しかし、最後のハング周り。
List<WeakReference<Person>> refs = Lists.newArrayList();
{
Person person = getPerson("abc");
refs.add(new WeakReference(person));
}
3番目のテストでは、私は、追加の一時的なスコープを追加し、明示的にコレクションに追加されません、追加のスコープの変数を使用します。適切に収集したコレクションのgetごみ内の参照を持つアイテムのすべて。
List<WeakReference<Person>> refs = Lists.newArrayList();
{
Person person = getPerson("abc");
refs.add(new WeakReference(person));
}
...
{
Person person = null;
}
そして、動作が変数に関連していた場合、私は興味があったので、第四のテストで、すべて同じ名前を持つ - 彼らは何とか同じ変数として解釈されていましたか? - 私は、一時的な変数のすべてに異なる名前を使用していました。期待通りに収集したコレクションのgetごみ内の参照を持つアイテムのすべて。
List<WeakReference<Person>> refs = Lists.newArrayList();
{
Person person1 = getPerson("abc");
refs.add(new WeakReference(person1));
}
...
{
Person person4 = null;
}
私が思い付くことができる唯一の説明は何とかJREは、それがスコープ外になるにもかかわらず、作成された最後のオブジェクトへの参照を維持していることです。しかし、私はそれを記述する任意のドキュメントを見ていません。
更新1:新しいテスト/回避策:
それはスコープの外に行く前に、私は明示的にnullにスコープ変数を設定した場合、オブジェクトは、私が期待するようガベージコレクションを取得します。
List<WeakReference<Person>> refs = Lists.newArrayList();
{
Person person = getPerson("abc");
refs.add(new WeakReference(person));
person = null;
}
2を更新:もう一つの新しいテスト:
新しい、余分なオブジェクトは、同じタイプである必要はありません。これは、罰金に動作します。
List<WeakReference<Person>> refs = Lists.newArrayList();
{
Person person = getPerson("abc");
refs.add(new WeakReference(person));
}
...
{
String unused = "unused string";
}
import com.google.common.base.MoreObjects;
import com.google.common.collect.Lists;
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.lang.ref.WeakReference;
import java.util.Iterator;
import java.util.List;
import java.util.Objects;
import static org.testng.Assert.assertEquals;
public class WeakReferenceCollectionTest {
private static final Logger logger = LoggerFactory.getLogger(WeakReferenceCollectionTest.class);
static class Person {
private String name;
public Person() {
}
public String getName() {
return name != null ? name : "<null>";
}
public Person setName(String name) {
this.name = name;
return this;
}
@Override
public String toString() {
return MoreObjects.toStringHelper(this)
.add("name", name)
.toString();
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
final Person person = (Person) o;
return Objects.equals(name, person.name);
}
@Override
public int hashCode() {
return Objects.hash(name);
}
}
@Test
public void collectionWorksAsExpected() throws InterruptedException {
List<WeakReference<Person>> refs = Lists.newArrayList();
refs.add(new WeakReference(getPerson("abc")));
refs.add(new WeakReference(getPerson("bcd")));
refs.add(new WeakReference(getPerson("cde")));
assertEquals(refs.size(), 3);
System.gc();
Thread.sleep(1000);
evictDeadRefs(refs);
assertEquals(refs.size(), 0);
refs.add(new WeakReference(getPerson("def")));
refs.add(new WeakReference(getPerson("efg")));
refs.add(new WeakReference(getPerson("fgh")));
assertEquals(refs.size(), 3);
System.gc();
Thread.sleep(1000);
evictDeadRefs(refs);
assertEquals(refs.size(), 0);
}
@Test
public void collectionWithScopesWorksDifferently() throws InterruptedException {
List<WeakReference<Person>> refs = Lists.newArrayList();
{
Person person = getPerson("abc");
refs.add(new WeakReference(person));
}
{
Person person = getPerson("bcd");
refs.add(new WeakReference(person));
}
{
Person person = getPerson("cde");
refs.add(new WeakReference(person));
}
assertEquals(refs.size(), 3);
System.gc();
Thread.sleep(1000);
evictDeadRefs(refs);
assertEquals(refs.size(), 1); // last one never goes away
assertEquals(refs.get(0).get().getName(), "cde");
{
Person person = getPerson("def");
refs.add(new WeakReference(person));
}
{
Person person = getPerson("efg");
refs.add(new WeakReference(person));
}
{
Person person = getPerson("fgh");
refs.add(new WeakReference(person));
}
assertEquals(refs.size(), 4); // previous last one is still in there
System.gc();
Thread.sleep(1000);
evictDeadRefs(refs);
assertEquals(refs.size(), 1); // last one never goes away
assertEquals(refs.get(0).get().getName(), "fgh");
}
@Test
public void collectionWithScopesAndNewVariableSetToNull() throws InterruptedException {
List<WeakReference<Person>> refs = Lists.newArrayList();
{
Person person = getPerson("abc");
refs.add(new WeakReference(person));
}
{
Person person = getPerson("bcd");
refs.add(new WeakReference(person));
}
{
Person person = getPerson("cde");
refs.add(new WeakReference(person));
}
{
Person person = null;
}
assertEquals(refs.size(), 3);
System.gc();
Thread.sleep(1000);
evictDeadRefs(refs);
assertEquals(refs.size(), 0);
}
@Test
public void collectionWithScopesAndDifferentVariableNames() throws InterruptedException {
List<WeakReference<Person>> refs = Lists.newArrayList();
{
Person person1 = getPerson("abc");
refs.add(new WeakReference(person1));
}
{
Person person2 = getPerson("bcd");
refs.add(new WeakReference(person2));
}
{
Person person3 = getPerson("cde");
refs.add(new WeakReference(person3));
}
{
Person person4 = null;
}
assertEquals(refs.size(), 3);
System.gc();
Thread.sleep(1000);
evictDeadRefs(refs);
assertEquals(refs.size(), 0);
}
@Test
public void collectionWithScopesAndExplicitlySetToNull() throws InterruptedException {
List<WeakReference<Person>> refs = Lists.newArrayList();
{
Person person = getPerson("abc");
refs.add(new WeakReference(person));
person = null;
}
{
Person person = getPerson("bcd");
refs.add(new WeakReference(person));
person = null;
}
{
Person person = getPerson("cde");
refs.add(new WeakReference(person));
person = null;
}
assertEquals(refs.size(), 3);
System.gc();
Thread.sleep(1000);
evictDeadRefs(refs);
assertEquals(refs.size(), 0);
}
@Test
public void createUnrelatedVariable() throws InterruptedException {
List<WeakReference<Person>> refs = Lists.newArrayList();
{
Person person = getPerson("abc");
refs.add(new WeakReference(person));
}
{
Person person = getPerson("bcd");
refs.add(new WeakReference(person));
}
{
Person person = getPerson("cde");
refs.add(new WeakReference(person));
}
{
String unused = "unused string";
}
assertEquals(refs.size(), 3);
System.gc();
Thread.sleep(1000);
evictDeadRefs(refs);
assertEquals(refs.size(), 0);
}
private void evictDeadRefs(List<WeakReference<Person>> refs) {
final Iterator<WeakReference<Person>> it = refs.iterator();
while (it.hasNext()) {
final WeakReference<Person> ref = it.next();
if (ref.get() == null) {
logger.debug("evictDeadRefs(): removing ref");
it.remove();
} else {
logger.debug("evictDeadRefs(): ref is not null: " + ref.get());
}
}
}
private Person getPerson(String s) {
return new Person().setName(s);
}
}
私はあなたのJavaコードをバイトコードにコンパイルされてどのようにいくつかの相互作用を見ていると思います。ノートには2つの重要なもの:
- ガベージコレクタは、オブジェクトが収集されるとき、あるいは、場合保証するものではありません。保証はされませんどのオブジェクトです。
- バイトコードは、「ローカル変数」を持っていません。代わりに、それは多くのスタックフレームで、地元のスタックを持っています。ローカル変数は、スタックフレーム内の特定位置に変換されます。
#1のので、ジャワのスコープ中括弧は、新しいスタックフレームとして実装する必要はありません。代わりに、Javaコンパイラは、全体の方法のための1つのスタックフレームを作成し、スコープ規則と一致している方法でそれを使用することができます。この手段は、第二の試験では、ローカル変数は、person
ガベージコレクションを防止する、方法が終了するまで住んでスタックフレームインデックスによって表されます。
そのため#2、およびそれらが使用される前に、ローカル変数を初期化しなければならないための、Javaコンパイラは限りそれらのどの2つが同時に範囲で、これまでではないとして、複数のローカル変数を表すために、スタックフレームの一つのインデックスを再利用することができます。このように、あなたの「異なる」のすべてperson
のテスト3と4エンドでのローカル変数、スタック上の同じ場所であることまで。
TL; DR:ガベージコレクションが矛盾しないように期待してはいけません。オブジェクトが収集されている場合は、使用しているJVM GCたことで、あなたのJavaコンパイラの実装の特定の詳細の両方によって影響を受けることができます。