java 编译器在分配指针并超出块范围时是否插入自由?

Does java compiler insert free when pointer is allocated and go out of scope in a block?

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

    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 的大小,因此将 ArrayList 重新分配给 array 保存 space
  2. 有某种编译器魔法或 gc 魔法可以在块作用域结束后立即释放和回收 ArrayList 使用的内存(例如像 rust),否则我们现在最多坐 2 space 的倍数,直到 gc 启动。

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

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

this answer所述:

Scope is a language concept that determines the validity of names. Whether an object can be garbage collected (and therefore finalized) depends on whether it is reachable.

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

您提出了可能的动机

  1. The size of ArrayList is bigger than the size of list, so reassigning ArrayList to array save space

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

还有另一个可能的原因,即随后使用普通数组比使用封装在 ArrayList 中的数组更有效的想法。但是,当然,这些构造之间的差异(如果有的话)几乎无关紧要。

说到深奥的优化,在调用 toArray 时指定初始数组大小被认为是一个优势,直到 someone measured and analyzed 才发现,即 myObjList = list.toArray(new MyObj[0]); 实际上会更多在现实生活中高效。

无论如何,我们无法了解作者的想法,这就是应该记录任何与直接代码的偏差的原因。

您的替代建议:

  1. There is some sort of compiler magic or gc magic that deallocates and reclaim the memory use by ArrayList immediately after the block scope ends (eg. like rust), otherwise we are now sitting on up to 2 times amount of space until gc kicks in.

没抓住重点。 Java 中的任何 space 优化都是关于最小化仍然存在的对象占用的内存量。无法访问的对象是否已被识别并不重要,它们无法访问就已经足够了,因此可能是可回收的。当确实需要内存时,垃圾收集器将 运行,即服务于新的分配请求。在那之前,未使用的内存是否包含旧对象并不重要。

所以代码可能是由 space 保存尝试激发的,在这方面,它是有效的,即使没有立即释放。如前所述,您可以通过在 ArrayList 上调用 trimToSize() 以更简单的方式实现相同的目的。但请注意,如果容量恰好与大小不匹配,trimToSize() 对数组的缩小在幕后并没有什么不同,它意味着创建一个新数组并让旧数组接受垃圾收集.

但是没有立即释放并且很少需要立即释放的事实应该允许得出这样的结论:space 这样的保存尝试只在实践中很重要,当结果对象应该持久化很长时间。当副本的生命周期短于下一次垃圾收集的时间时,它不会保存任何东西,剩下的就是不必要的副本创建。由于我们无法预测下一次垃圾回收的时间,我们只能对对象的预期生命周期(长或短)做一个粗略的分类...

一般方法是假设在大多数情况下,ArrayList 的更高容量不是问题,性能增益更重要。这就是 class 首先保持较高容量的原因。

不,这样做的原因与代码中添加空行的原因相同。

块中的变量仅限于该块,在块之后不能再使用。所以不需要关注那些块变量。

所以这更具可读性:

A a;
{ B b; C c; ... }
...

比:

A a;
B b;
C c;
...
...

这是一种使代码更具可读性的尝试。例如上面的例子可以读作“A a; 的声明,然后是一个可能填充 a.

的块

JVM 中的生命周期分析很好。正如完全没有必要在使用结束时将变量设置为 null 一样。

有时块也被滥用来重复具有相同局部变量的块:

A a1;
{ B b; C c; ... a1 ... }
A a2;
{ B b; C c; ... a2 ... }
A a3;
{ B b; C c; ... a3 ... }

不用说,这与使代码更好的风格背道而驰。