java中100万个引用集合中的内存分配
我创建了一个包含100万个MyItem对象的ArrayList,消耗的内存为106mb(从任务管理器中检查),但通过addAll()方法将同一个列表添加到另外两个列表后,需要259mb。我的问题是我只添加了对列表的引用,在这100万之后没有创建新对象。为什么即使使用了LinkedList,内存消耗仍会增加(因为它不需要连续的内存块,所以不会进行重新分配) 如何有效地实现这一点?数据在我的程序中通过各种列表,并消耗超过1GB的内存java中100万个引用集合中的内存分配,java,memory-management,arraylist,reference,linked-list,Java,Memory Management,Arraylist,Reference,Linked List,我创建了一个包含100万个MyItem对象的ArrayList,消耗的内存为106mb(从任务管理器中检查),但通过addAll()方法将同一个列表添加到另外两个列表后,需要259mb。我的问题是我只添加了对列表的引用,在这100万之后没有创建新对象。为什么即使使用了LinkedList,内存消耗仍会增加(因为它不需要连续的内存块,所以不会进行重新分配) 如何有效地实现这一点?数据在我的程序中通过各种列表,并消耗超过1GB的内存 public class MyItem{ private Stri
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
}
}
公共类MyItem{
私有字符串;
私有int-id;
私有字符串搜索;
公共MyItem(字符串s,整数id){
这个.s=s;
this.id=id;
}
公共字符串get(){
返回s;
}
公共int getId(){
返回id;
}
公共字符串getSearchParameter(){
返回搜索;
}
public void setSearchParameter(字符串s){
搜索=s;
}
}
公共班机{
公共静态void main(字符串参数[]){
列表l=新的ArrayList();
列表=新建LinkedList();
List list1=新链接列表();
对于(int i=0;i<1000000;i++){
MyItem m=新的MyItem(“你好”+i,i+1);
m、 setSearchParameter(m.getS());
l、 加(i,m);
}
列表。添加全部(l);
清单1.addAll(l);
列表1.addAll(列表);
扫描仪s=新的扫描仪(System.in);
s、 next();//只是不终止
}
}
任何时候你在幕后调用它都会为每个添加的元素创建一个节点,因此你在这里创建了300万个这样的节点,这不是免费的,事实上:
32位JVM
和64位JVM
上引用的大小是4个字节
,使用UseCompressedOops
(-XX:+UseCompressedOops)启用,默认情况下,在Java 7和更高版本中,堆小于32 GB,在64位JVM上8字节,禁用UseCompressedOops
(-XX:-UseCompressedOops)。因此,根据您的配置,这里给出了12字节或24字节
32位JVM
上为8字节,在64位JVM上为16字节。因此,根据您的配置,这里给出了8个字节或16个字节
32位JVM上的每个实例20字节
64位JVM
上启用了UseCompressedOops
时,每个实例的28字节UseCompressedOops
LinkedList
上对100万个对象进行3次addAll
调用时,它会给出
32位JVM上
64位JVM
上启用UseCompressedOops
64位JVM的情况下
ArrayList
后尝试调用System.gc()
,以获取实际大小,并在加载LinkedList
后执行相同的操作
如果要获得给定对象的大小,可以使用
如果您使用的是64位JVM
,并且想知道是否启用了UseCompressedOops
,只需在一个只有-X
选项的终端中启动java命令,并添加-XX:
,例如,如果我的命令是java-Xms4g-Xmx4g-XX:MaxPermSize=4g-cp
,启动java-Xms4g-Xmx4g-XX:MaxPermSize=4g-XX:+PrintFlagsFinal | grep UseCompressedOops,输出的开头应该如下所示:
bool UseCompressedOops := true {lp64_product}
...
在这种情况下,标志
UseCompressedOops
已启用LinkedList
是a,因此列表中的元素由节点表示,每个节点包含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;
}
}
私有静态类节点{
E项目;
节点下一步;
节点前置;
节点(上一个节点、E元素、下一个节点){
this.item=元素;
this.next=next;
this.prev=prev;
}
}
由于使用了大量内存,可能没有使用压缩OOP,因此引用可能是64位的,即每个引用8字节
对于每个引用16字节+8字节的对象头,节点占用40字节。如果有100万个元素,这将是40 Mb
两个列表是80MB,然后请记住Java内存被分割成池,对象(节点)被移动,另外153MB的内存消耗现在看起来差不多了
注意:
Arraylist
每个元素只使用8个字节,而不是40个字节,如果您预先分配备份数组(因为您知道数组的大小,所以可以这样做),这样可以节省大量内存。我想LinkedList.Node将被分配引用!如果没有,我如何在不分配更多节点的情况下工作;(感谢您的回复!它就是这样做的,但它会为每个添加的元素创建一个LinkedList.Node实例,以保留上一个和下一个节点ArrayList根据其名称在数组中表示。如果数组太小,则会创建两倍的大小,并将引用复制到新数组中。因为Java有自己的内存管理第一个数组的内存将仅释放给JVM内部。由于您使用了大量内存,可能没有使用压缩OOP,默认情况下,如果堆小于32 GB,64位JVM上会启用UseCompressedOops,如果不是这样,我会感到惊讶,因为这只是一个简单的问题