Jeg*_*abu 5 java memory-management reference linked-list arraylist
我创建了一个包含 100 万个 MyItem 对象的 ArrayList,消耗的内存为 106mb(从任务管理器检查)但是通过 addAll() 方法将相同的列表添加到另外两个列表后,需要 259mb。我的问题是我只添加了对 list 的引用,在 100 万个之后没有创建新对象。为什么即使使用 LinkedList 内存消耗也会增加(因为它不需要连续的内存块,因此不会进行重新分配)?
如何高效地实现这一目标?数据在我的程序中经过各种列表,消耗了超过 1GB 的内存。上面介绍了类似的场景。
public class MyItem{
private String s;
private int id;
private String search;
public MyItem(String s, int id) {
this.s = s;
this.id = id;
}
public String getS() {
return s;
}
public int getId() {
return id;
}
public String getSearchParameter() {
return search;
}
public void setSearchParameter(String s) {
search = s;
}
}
public class Main{
public static void main(String args[]) {
List<MyItem> l = new ArrayList<>();
List<MyItem> list = new LinkedList<>();
List<MyItem> list1 = new LinkedList<>();
for (int i = 0; i < 1000000 ; i++) {
MyItem m = new MyItem("hello "+i ,i+1);
m.setSearchParameter(m.getS());
l.add(i,m);
}
list.addAll(l);
list1.addAll(l);
list1.addAll(list);
Scanner s = new Scanner(System.in);
s.next();//just not to terminate
}
}
Run Code Online (Sandbox Code Playgroud)
任何时候你在LinkedList.addAll幕后调用,它都会LinkedList.Node为每个添加的元素创建一个,所以在这里你创建了300 万个这样的节点,这确实不是免费的:
4 bytes打开32-bit JVM并64-bit JVM启用UseCompressedOops(-XX:+UseCompressedOops),默认情况下,堆小于32 GBJava 7 及更高版本,并且8 bytes已64-bit JVM禁用UseCompressedOops(-XX:-UseCompressedOops) )。所以这里根据你的配置它给出12 字节或24 字节。8 byteson32-bit JVM和16 byteson 64-bit JVM。所以这里根据你的配置它给出8 字节或16 字节。因此,如果我们总结一下,需要:
32-bit JVM64-bit JVM后每个实例28 字节UseCompressedOops64-bit JVM禁用UseCompressedOops时每个实例40 字节当您addAll在 a 上调用 100 万个对象中的3 次时LinkedList,它会给出
32-bit JVM64-bit JVM启用UseCompressedOops64-bit JVMUseCompressedOops120残疾人其余的可能是垃圾收集器尚未收集的对象,您应该尝试调用System.gc()在加载后调用ArrayList以获取实际大小,并在加载LinkedList.
如果你想获取给定对象的大小,你可以使用SizeOf。
如果您使用 a64-bit JVM并且想知道是否UseCompressedOops已启用,只需在终端中启动您的 java 命令,仅使用-X选项并添加-XX:+PrintFlagsFinal | grep UseCompressedOops,例如,如果我的命令是java -Xms4g -Xmx4g -XX:MaxPermSize=4g -cp <something> <my-class>,则启动java -Xms4g -Xmx4g -XX:MaxPermSize=4g -XX:+PrintFlagsFinal | grep UseCompressedOops,则输出的开头应如下所示:
bool UseCompressedOops := true {lp64_product}
...
Run Code Online (Sandbox Code Playgroud)
在这种情况下,该标志UseCompressedOops已启用
LinkedList是一个双向链表,因此列表中的元素由节点表示,每个节点包含3个引用。
从 Java 8 开始:
private static class Node<E> {
E item;
Node<E> next;
Node<E> prev;
Node(Node<E> prev, E element, Node<E> next) {
this.item = element;
this.next = next;
this.prev = prev;
}
}
Run Code Online (Sandbox Code Playgroud)
由于您使用大量内存,因此您可能没有使用压缩的 OOP,因此引用可能是 64 位,即每个引用 8 个字节。
如果对象头为 16 字节 + 每个引用 8 字节,则一个节点占用 40 字节。如果有 100 万个元素,则大小为 40 Mb。
两个列表是 80 Mb,然后记住 Java 内存被分割成池,并且对象(节点)会四处移动,现在额外消耗 153 Mb 的内存似乎是正确的。
注意:每个Arraylist元素仅使用 8 个字节,而不是 40 个字节,并且如果您预先分配后备数组(因为您知道大小,所以可以这样做),这样可以节省大量内存。