ガーベッジになってくれないクラス。

GcArray.javaを実行すると、最後にはメモリ不足のエラーになるのですが、途中では次のように何回かガーベッジコレクションが働いている形跡が。

残りメモリ = 130624
残りメモリ = 125408
残りメモリ = 1802192
残りメモリ = 1802192

これで何回か途中で実行を停止させ、コードを見直すということを繰り返したのですが、考えてみたらガーベッジとして回収されるものもあるからですね。次のようにメモリ残量を各タイミングで実行するようにコードを変更して実行してみました。

import java.util.*;

public class GcArray2 {
  static ArrayList<int[]> list = new ArrayList<int[]>();
  public static void main(String[] args) {
    while (true) {
      System.out.println("block start        : 残りメモリ = " + Runtime.getRuntime().freeMemory());
      int[] a = new int[1000];
      System.out.println("a[1000] generated  : 残りメモリ = " + Runtime.getRuntime().freeMemory());
      for (int i = 0; i < 1000; i++) {
        a[i] = i;
      }
      System.out.println("a[1000] used       : 残りメモリ = " + Runtime.getRuntime().freeMemory());
      list.add(a);
      System.out.println("list added a[1000] : 残りメモリ = " + Runtime.getRuntime().freeMemory());
    }
  }
}

実行結果を見ると、a[1000]のメモリと、listにa[1000]が追加された分のメモリは別々に確保されていることが分かります。

block start        : 残りメモリ = 1838280
 │
 │ (※1)a[1000]のメモリ4,016バイトが確保されている
 ↓
a[1000] generated  : 残りメモリ = 1834264
a[1000] used       : 残りメモリ = 1834264
 │
 │ (※2)listにa[1000]を追加した分のメモリ10,640バイトが確保されている
 ↓
list added a[1000] : 残りメモリ = 1823624
 │
 │ (※1)(※2)分のメモリは確保されたまま
 ↓
block start        : 残りメモリ = 1823624

ということは、a[1000]の分のメモリは次のループに入った時点でガーベジになっていて、これはガーベジコレクションの処理対象。ですが、listの方はまだ参照されているので対象外。このため、a[1000]の分のメモリを何度かガーベジコレクションで回収して実行が続けられますが、list分は回収できないまま膨れ上がるので、最後にはOutOfMemoryErrorになります。
うーん、それにしても、a[1000]生成に必要なメモリより、a[1000]をlistに加えるのに必要なメモリの方が多い(それも倍)のはなぜでしょう。ArrayListってかなりメモリ利用効率が悪いのでしょうか?