首页 > 解决方案 > java编译器在分配指针并超出块范围时是否插入空闲?

问题描述

我正在挠头试图理解以下代码的重点

    Map<String Set<MyOtherObj>> myMap = myapi.getMyMap(); 

    final MyObj[] myObjList;
    {
        final List<MyObj> list = new ArrayList<>(myMap.size());
        for (Entry<String, Set<MyOtherObj>> entry : myMap.entrySet()) {
            final int myCount = MyUtility.getCount(entry.getValue());
            if (myCount <= 0)
                continue;
            list.add(new MyObj(entry.getKey(), myCount));
        }
        if (list.isEmpty())
            return;
        myObjList = list.toArray(new MyObj[list.size()]);
    }

可以改写成以下

    Map<String Set<MyOtherObj>> myMap = myapi.getMyMap(); 

    final List<MyObj> list = new ArrayList<>(myMap.size());
    for (Entry<String, Set<MyOtherObj>> entry : myMap.entrySet()) {
        final int myCount = MyUtility.getCount(entry.getValue());
        if (myCount <= 0)
            continue;
        list.add(new MyObj(entry.getKey(), myCount));
    }
    if (list.isEmpty())
        return;

我能想到为什么我们把它ArrayList放在一个块中然后将内容重新分配给一个数组的唯一原因是

  1. 的大小ArrayList大于 的大小list,因此重新分配ArrayListarray节省空间
  2. 有某种编译器魔法或 gc 魔法可以ArrayList在块作用域结束后立即释放和回收内存使用(例如,像 rust),否则我们现在坐在高达 2 倍的空间量直到 gc 启动。

所以我的问题是,第一个代码示例是否有意义,它是否更有效?

此代码当前每秒执行 20k 条消息。

标签: javagarbage-collection

解决方案


本答案所述:

范围是确定名称有效性的语言概念。一个对象是否可以被垃圾回收(并因此最终确定)取决于它是否是可到达的。

所以,不,范围与垃圾收集无关,但对于可维护的代码,建议将名称限制在其用途所需的最小范围内。但是,这不适用于您的场景,其中引入了一个新名称来表示显然仍然需要的相同事物。

你提出了可能的动机

  1. 的大小ArrayList大于 的大小list,因此重新分配ArrayListarray节省空间

但是您可以在将变量声明listArrayList<MyObj>而不是在填充它之后List<MyObj>调用它时实现相同的效果。trimToSize()

还有另一个可能的原因,即后来使用普通数组比使用封装在ArrayList. 但是,当然,这些构造之间的差异(如果有的话)很少重要。

说到深奥的优化,在调用时指定初始数组大小toArray被认为是一种优势,直到有人测量和分析,发现这一点,即myObjList = list.toArray(new MyObj[0]);在现实生活中实际上会更有效。

无论如何,我们无法调查作者的想法,这就是为什么任何与直接代码的偏差都应该记录在案的原因。

您的替代建议:

  1. 有某种编译器魔法或 gc 魔法可以ArrayList在块作用域结束后立即释放和回收内存使用(例如,像 rust),否则我们现在坐在高达 2 倍的空间量直到 gc 启动。

没有抓住重点。Java 中的任何空间优化都是关于最小化仍然存在的对象占用的内存量。不可达对象是否被标识为这样并不重要,它们不可达就足够了,因此,它们可能是可回收的。当实际需要内存时,垃圾收集器将运行,即为新的分配请求提供服务。在那之前,未使用的内存是否包含旧对象并不重要。

所以代码可能是出于节省空间的尝试,在这方面,它是有效的,即使没有立即释放。如前所述,您可以通过调用trimToSize(). ArrayList但是请注意,如果容量与大小不匹配,trimToSize()则数组的缩小在幕后的工作方式并没有什么不同,这意味着创建一个新数组并让旧数组成为垃圾回收的对象。

但是没有立即释放并且很少需要立即释放的事实应该可以得出这样的结论,即这样的空间节省尝试仅在实践中很重要,当结果对象应该持续很长时间时。当副本的生命周期比下一次垃圾回收的时间短时,它没有保存任何东西,剩下的就是不必要的副本创建。由于我们无法预测下一次垃圾回收的时间,我们只能对对象的预期生命周期(长或不长)进行粗略的分类……</p>

一般的做法是假设在大多数情况下,更大的容量ArrayList不是问题,性能增益更重要。这就是为什么这个类首先保持更高的容量。


推荐阅读