Warning: file_get_contents(/data/phpspider/zhask/data//catemap/9/java/315.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Java 是否有一种习惯用法/模式可以传递集合而不保留对它的引用或阻止进一步使用?_Java_Design Patterns - Fatal编程技术网

Java 是否有一种习惯用法/模式可以传递集合而不保留对它的引用或阻止进一步使用?

Java 是否有一种习惯用法/模式可以传递集合而不保留对它的引用或阻止进一步使用?,java,design-patterns,Java,Design Patterns,我正在清理一些开始在生产中抛出java.lang.OutOfMemoryError的代码 问题区域有两种处理大型集合的方法,例如: public void doSomething(Collection<HeavyObject> inputs) { ... do some stuff using INPUTS, deriving some different objects ... ... do some other stuff NOT using INPUTS, only

我正在清理一些开始在生产中抛出
java.lang.OutOfMemoryError
的代码

问题区域有两种处理大型集合的方法,例如:

public void doSomething(Collection<HeavyObject> inputs) {

  ... do some stuff using INPUTS, deriving some different objects ...

  ... do some other stuff NOT using INPUTS, only derived objects ...

}

public void unsuspectingCaller() {
  Collection<HeavyObject> largeCollection;
  ... some stuff to populate the collection ...
  doSomething(largeCollection);
  ... other stuff ...
   // this following code may be added in the future
  kaboom(largeCollection); // walks into maintenance trap!
}

陈述挑战的另一种方式是如何将doSomething()中的内存使用量从三倍减少到两倍,这是一种惯用的方式,可以在编译时强制安全使用

我希望有更好的方法,但我想到了以下几点:

doSomething()
重构为一个类

class DoerOfSomething {
   public DoerOfSomething(Collection<HeavyObject> inputs) {
     ... do some stuff using INPUTS, deriving other objects ...
     // derived objects are set as members
     // inputs goes out of scope
   } 
   public void doSomething() {
     ... do some other stuff NOT using INPUTS, only derived objects ...
   }
}
类做某事{
公共数据(收集输入){
…使用输入做一些事情,派生其他对象。。。
//派生对象被设置为成员
//输入超出范围
} 
公共无效剂量测定法(){
…不使用输入,只使用派生对象执行其他操作。。。
}
}

现在,调用方可以执行自己的分析,以查看调用
targets.clear()
是否合适。

调用
targets.clear()
的问题是(正如您所指出的)可能有其他东西在使用集合。下面是我的方法:

public void doSomething(Collection<Widget> targets) {
  // ... do some stuff using TARGETS ...

  targets = null;

  // ... do some other stuff NOT using TARGETS ...
}
在第一个示例中,JVM应该能够在调用启动后判断调用方没有可访问的引用。在第二个示例中,JVM更难知道。但在这两种情况下,您都依赖于JVM来检测调用方中的引用实际上是不可访问的

更新

我怀疑
MemoryTest
示例失败的原因是
doSomething
代码正在创建一个临时变量,或者使用寄存器或其他东西来保存对
流的引用。JVM可能没有意识到该变量/寄存器不再有效,因此可能会将
对象视为可访问的。但是
对象很可能有一个对原始集合的引用,这将使集合也可以访问

可以说这是一个JVM错误,但我不这么认为。JLS和JVM对于JVM是否/何时应该检测到在方法调用中使用的局部变量(或临时变量/寄存器)不再是可访问的,没有做出强有力的声明


但我真的认为波希米亚人给了你最好的答案。(没有。我不认为他是在开玩笑。

如果您必须对此进行微优化,以将问题压缩到当前(小)堆的内存占用中,那么简单的解决方案是将堆变大

正如您所注意到的,您可以通过各种巧妙的方法来优化存储利用率(例如,通过清除内容),实际上可能会破坏应用程序或使其更难维护


(您的
MemoryTest
示例很好地说明了巧妙的优化可能会失败。幕后发生的事情很难预测。)

我之所以发布这篇文章,是因为它太大了,无法发表评论:

import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.IntStream;

public class MemoryTest {

    public static void main(String[] args) {
        new MemoryTest().doSomething(List.of(new HeavyObject(), new HeavyObject(), new HeavyObject()));
    }


    static class HeavyObject {
        int[] oneGigabyte = IntStream.range(0, 256_000_000).toArray();
        public int[] getGig() {
            return oneGigabyte;
        }
    }

    private int[] skynet(int[] in) {
        // perform out-of-this-world artificial intelligence computation
        return Arrays.stream(in)
                .map(x -> x >> 1)
                .toArray();
    }


    void doSomething(Collection<HeavyObject> input) {
        Collection<int[]> doubleMemoryUsage = input.stream().map(HeavyObject::getGig).map(this::skynet).collect(Collectors
                .toList());

        input = null;

        Collection<int[]> tripleMemoryUsage = doubleMemoryUsage.stream().map(this::skynet).collect(Collectors.toList());

        double sum = tripleMemoryUsage.stream().flatMapToDouble(array -> Arrays.stream(array).asDoubleStream()).sum();
        System.out.println("sum = " + sum);
    }

}

它也不应该失败,但它确实失败了。即使使用
ZGC
Shenandoah
。但是,我不知道为什么。

如果您对此感到担忧,那么简单地将集合清除为“0”项是否会起作用(例如,您的
targets.clear()
)会起作用?它“起作用”,但会触发维护陷阱并导致毫无戒备的调用方中断,无论是现在还是将来,分配更多内存如何?显然,“理想”是在“集合”在范围内时立即初始化它,并在不再需要它时立即构造程序,使其超出范围(因此有资格释放JVM)。但这将涉及重组你的整个应用程序——可能不是一个选项。C++总是有的。在Java.hi@Stephen中是一个相当类似的成语。在方法中将其设置为
null
,对GC没有任何帮助,因为调用方中对集合的引用仍然是活动的。事实上,在重新阅读时,我现在想知道
这是否应该是正确的
代码的实际行为是否不同。我得测试一下。唉,它没有work@AlexR我不能完全肯定这个例子能证明什么。如果我在main方法中隔离调用方1,比如:
publicstaticvoidmain(String[]args){newmemorytest().doSomething(Arrays.asList(newheavyObject(),newheavyObject(),newheavyObject());}
;执行
java-Xms6g-Xmx6g MemoryTest.java
它运行得很好。如果我注释
inputs=null
,它将失败。正如这个答案所说的那样。@Eugene您介意发布您的实际测试代码吗?我不确定我是否理解。好吧,这是我没有预料到的:你是对的
java-Xms6g-Xmx6g MemoryTest.java
与从命令行启动的Oracle jdk-15一起工作。但是,从Eclipse->RunAs Java程序中启动时,使用相同的-Xms6g-Xmx6g参数,指向相同的jdk-15,在OOM中失败!我能看到的唯一区别是Eclipse运行的是
javaw
,而不是
java
@AlexR。我已经多年没有使用Eclipse了,也无法测试它,但命令行对我来说是事实。总是。将参数设置为
null
以帮助GC仍然在JDK代码中使用,请查看
LinkedList
LinkedBlockingQueue
// This should be OK
doSomething(computeWidgets(...));

// This may be a problem
Collection<Widget> targets = computeWidgets(...);
doSomething(targets);
// Don't use 'targets' from now on.
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.IntStream;

public class MemoryTest {

    public static void main(String[] args) {
        new MemoryTest().doSomething(List.of(new HeavyObject(), new HeavyObject(), new HeavyObject()));
    }


    static class HeavyObject {
        int[] oneGigabyte = IntStream.range(0, 256_000_000).toArray();
        public int[] getGig() {
            return oneGigabyte;
        }
    }

    private int[] skynet(int[] in) {
        // perform out-of-this-world artificial intelligence computation
        return Arrays.stream(in)
                .map(x -> x >> 1)
                .toArray();
    }


    void doSomething(Collection<HeavyObject> input) {
        Collection<int[]> doubleMemoryUsage = input.stream().map(HeavyObject::getGig).map(this::skynet).collect(Collectors
                .toList());

        input = null;

        Collection<int[]> tripleMemoryUsage = doubleMemoryUsage.stream().map(this::skynet).collect(Collectors.toList());

        double sum = tripleMemoryUsage.stream().flatMapToDouble(array -> Arrays.stream(array).asDoubleStream()).sum();
        System.out.println("sum = " + sum);
    }

}
void doSomething() {
    Collection<HeavyObject> input = List.of(new HeavyObject(), new HeavyObject(), new HeavyObject());
    Collection<int[]> doubleMemoryUsage = input.stream().map(HeavyObject::getGig).map(this::skynet).collect(Collectors
            .toList());

    //input = null;

    Collection<int[]> tripleMemoryUsage = doubleMemoryUsage.stream().map(this::skynet).collect(Collectors.toList());

    double sum = tripleMemoryUsage.stream().flatMapToDouble(array -> Arrays.stream(array).asDoubleStream()).sum();
    System.out.println("sum = " + sum);
}