在java中读取大型数据文件时会产生巨大的内存开销

在java中读取大型数据文件时会产生巨大的内存开销,java,memory,Java,Memory,我正在做深度学习神经网络开发,使用MNIST数据集进行测试。训练集由60000个序列组成,每个序列有784个双精度输入值。在java中将这些数据从文件读入数组的过程中,不知何故会产生大约4GB的内存开销,在整个程序运行过程中都会保持分配。此开销是为双精度阵列本身分配的60000*784*8=376MB的额外开销。这种开销很可能是因为java除了存储数字数组外,还存储了文件的完整副本,但这可能是扫描仪的开销 据消息来源称,将文件作为流读取可以避免将整个文件存储在内存中。但是,流读取仍然存在这个问题

我正在做深度学习神经网络开发,使用MNIST数据集进行测试。训练集由60000个序列组成,每个序列有784个双精度输入值。在java中将这些数据从文件读入数组的过程中,不知何故会产生大约4GB的内存开销,在整个程序运行过程中都会保持分配。此开销是为双精度阵列本身分配的60000*784*8=376MB的额外开销。这种开销很可能是因为java除了存储数字数组外,还存储了文件的完整副本,但这可能是扫描仪的开销

据消息来源称,将文件作为流读取可以避免将整个文件存储在内存中。但是,流读取仍然存在这个问题。我将Java 8与Intellij 2016.2.4一起使用。这是流读取代码:

FileInputStream inputStream = null;
Scanner fileScan = null;
String line;
String[] numbersAsStrings;

totalTrainingSequenceArray = new double[60000][784];

try {
    inputStream = new FileInputStream(m_sequenceFile);
    fileScan = new Scanner(inputStream, "UTF-8");
    int sequenceNum = 0;
    line = fileScan.nextLine();//Read and discard the first line.
    while (fileScan.hasNextLine()) {
        line = fileScan.nextLine();
        numbersAsStrings = line.split("\\s+"); //Split the line into an array of strings using any whitespace delimiter.
        for (int inputPosition = 0; inputPosition < m_numInputs; inputPosition++) {
            totalTrainingSequenceArray[sequenceNum][inputPosition] = Double.parseDouble(numbersAsStrings[inputPosition]);
        }
        sequenceNum++;
    }
    if (fileScan.ioException() != null) {//Handle fileScan exception
        throw fileScan.ioException();
    }
} catch (IOException e) {//Handle the inputstream exception
    e.printStackTrace();
} finally {
    if (inputStream != null)  {
        try {
            inputStream.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    if (fileScan != null) {
        fileScan.close();
    }
}
FileInputStream-inputStream=null;
Scanner fileScan=null;
弦线;
字符串[]数字字符串;
totalTrainingSequenceArray=新的双精度[60000][784];
试一试{
inputStream=新文件inputStream(m_sequenceFile);
fileScan=新扫描仪(inputStream,“UTF-8”);
int sequenceNum=0;
line=fileScan.nextLine();//读取并放弃第一行。
while(fileScan.hasNextLine()){
line=fileScan.nextLine();
numbersAsStrings=line.split(\\s+);//使用任何空格分隔符将行拆分为字符串数组。
对于(int-inputPosition=0;inputPosition

在读取并调用System.gc()之后,我尝试将流和扫描程序设置为null,但这没有任何作用。这是扫描仪开销问题吗?读取这个大数据文件而不产生大量永久性开销的最简单方法是什么?感谢您的任何输入。

您的代码工作正常。在完全GC之后,将实际使用380MB的堆

Java渴望分配内存以最小化GC开销,您可以通过使用
-Xmx512m
参数或使用不同的GC限制分配内存的大小,例如
-XX:+useConMarkSweepGC
或通过
-XX:MaxHeapFreeRatio=40
定义“开销”。VM使用分配的堆来平衡垃圾收集时间和执行速度(有一些螺丝钉可以影响它的决定)

通常是VM让堆填充,直到达到gc阈值,然后收集可以收集的任何垃圾,然后继续执行(这简化了很多)。这导致堆使用率出现“锯齿”模式(逐渐填充,然后突然降低堆使用率)。对于以一定速率产生垃圾的代码来说,这是完全正常的

您可以影响的点是“齿”可以构建的高度(通过调整允许的堆和/或gc应该何时启动)。垃圾创建的速度(堆使用率上升的幅度)取决于执行的代码,其范围可以从零到可达到的最大分配率

您的读取代码属于创建大量小垃圾对象的类型:来自扫描仪的线,您将线拆分为的部分。如果堆足够大,则可以在不收集任何垃圾的情况下读取整个文件(4GB堆设置很可能就是这种情况)

如果将堆变小,VM将更快地收集垃圾,从而减少内存使用量(同样,您可以使用gc参数强制收集堆使用量的较小百分比)

但是,期望代码只使用为数组计算的内存量运行是不合理的。您在任务管理器中看到的只是VM使用的所有内存的累积。这包括堆栈、JRE所需的任何资源、本机库和堆

堆外的内存可能变化很大,这取决于程序使用的线程、文件和其他资源的数量。作为一个非常粗略的经验法则,JRE本身至少要使用20-50MB,即使只是运行一些简单的东西,比如“Hello world”

无论您是调整堆大小还是微调gc参数,VM调优的问题在于每当问题集发生变化时都必须重新进行(例如,对于当前文件,您可能不需要使用-Xmx512m,但您需要调整下一个文件的值)

或者,您可以尝试减少创建的垃圾量,理想情况下为零。您可以逐行读取字符,并使用状态机进行解析,而不是扫描仪。这将大大减少垃圾创建,但会使代码更加复杂


在许多情况下,最“有效”的解决方案就是不用担心内存使用情况——优化VM参数或代码所花的时间可能会更有效地花在关注程序进展上。只要“开销”不妨碍您,何必麻烦呢?

您是如何测量内存使用率的?如果您使用Java 8,您可以从
Files.lines()
method.NPE-我正在使用Windows Task Manager.assylias测量内存-我将签出Files.line(),谢谢。我建议使用VisualVM查看堆的实际使用情况以及GC的行为……感谢您的回复。大量分配(大部分是由于我的高