【问题标题】:Error : java.lang.OutOfMemoryError: Java heap space错误:java.lang.OutOfMemoryError:Java 堆空间
【发布时间】:2016-01-19 09:01:19
【问题描述】:

我有这个“java.lang.OutOfMemoryError: Java heap space”,我阅读并理解我可以使用 -Xmx1024m 增加我的内存。但我认为在我的代码中我可以改变一些东西,这个错误不再发生。

首先,这是来自 VisualVM 的关于我的记忆的图像:

在图像中你可以看到对象“Pedidos”不是那么大,我有另一个对象“Enderecos”,它的大小越来越少但不完整,因为在对象完成之前我有错误.

重点是:

  • 我有 2 个类搜索一个大的 csv 文件(每个类 400.000 个值),我将展示代码。我尝试使用垃圾收集器,将变量设置为空,但无法正常工作,有人可以帮我吗?这是“Pedidos”类的代码,“Enderecos”类是相同的,我的项目只是调用这两个类。


// all Imports
import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.io.Reader;
import java.util.ArrayList;
import javax.swing.JOptionPane;
import Objetos.Pedido;

// CLASS
public class GerenciadorPedido{
    // ArrayList I will add all the "Pedidos" Objects
    ArrayList<Pedido> listaPedidos = new ArrayList<Pedido>();

    // Int that I need to use the values correctly
    int helper;

    // I create this global because I didnt want to create a new String everytime the for is running (trying to use less memory)
    String Campo[];
    String Linha;
    String newLinha;

    public ArrayList<Pedido> getListaPedidos() throws IOException {


        // Here I change the "\" and "/" to be accepted be the FILE (the csv address) 
        String Enderecotemp = System.getProperty("user.dir"), Endereco = "";
        char a;
        for (int i = 0; i < Enderecotemp.length(); i++) {
            a = Enderecotemp.charAt(i);
            if (a == '\\') a = '/';
            Endereco = Endereco + String.valueOf(a);
        }
        Endereco = Endereco + "/Pedido.csv";


        // Open the CSV File and the reader to read it
        File NovoArquivo = new File(Endereco);
        Reader FileLer = null;

        // Try to read the File
        try
        {
            FileLer = new FileReader(NovoArquivo);
        }

        catch(FileNotFoundException e) {
            JOptionPane.showMessageDialog(null, "Erro, fale com o Vini <Arquivo de Pedido Não Encontrado>");
        }

        // Read the File
        BufferedReader Lendo = new BufferedReader(FileLer);
        try
        {
            // Do for each line of the csv
            while (Lendo.ready()) {

                // Read the line and replace the caracteres ( needed to funcionality works )
                Linha = Lendo.readLine();
                newLinha = Linha.replaceAll("\"", "");
                newLinha = newLinha.replaceAll(",,", ", , ");
                newLinha = newLinha.replaceAll(",,", ", , ");
                newLinha = newLinha + " ";

                // Create Campo[x] for each value between ","
                Campo = newLinha.split(",");

                // Object 
                Pedido pedido = new Pedido();

                helper = 0;

                // Just to complete the object with the right values if the Campo.length have 15, 16, 17, 18 or 19 of size. 
                switch (Campo.length) {
                    case 15: pedido.setAddress1(Campo[9]);
                        break;
                    case 16: pedido.setAddress1(Campo[9] + Campo[10]);
                        helper = 1;
                        break;
                    case 17: pedido.setAddress1(Campo[9] + Campo[10] + Campo[11]);
                        helper = 2;
                        break;
                    case 18: pedido.setAddress1(Campo[9] + Campo[10] + Campo[11] + Campo[12]);
                        helper = 3;
                        break;
                    case 19: pedido.setAddress1(Campo[9] + Campo[10] + Campo[11] + Campo[12] + Campo[13]);
                        helper = 4;
                        break;
                }

                // Complete the Object
                pedido.setOrder(Campo[0]);
                pedido.setOrderValue(Float.parseFloat(Campo[1]));
                pedido.setOrderPv(Float.parseFloat(Campo[2]));
                pedido.setCombinedOrderFlag(Campo[3]);
                pedido.setCombineOrder(Campo[4]);
                pedido.setOrderType(Campo[5]);
                pedido.setOrderShipped(Campo[6]);
                pedido.setOrderCancelled(Campo[7]);
                pedido.setTransactionType(Campo[8]);
                pedido.setAddress2(Campo[10 + helper]);
                pedido.setAddress3(Campo[11 + helper]);
                pedido.setPost(Campo[12 + helper]);
                pedido.setCity(Campo[13 + helper]);
                pedido.setState(Campo[14 + helper]);

                // Add the object in the ArrayList
                listaPedidos.add(pedido);

                // Set everything to null to start again
                Campo = null;
                Linha = null;
                newLinha = null;
            }
        }

        catch(IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        finally
        {
            // Close the file and run garbage collector to try to clear the trash
            Lendo.close();
            FileLer.close();
            System.gc();
        }
        // return the ArrayList.
        return listaPedidos;
    }
}

该项目运行这个类,但是当项目尝试运行另一个(与这个相同,只更改名称和 csv )时,我有内存错误。我不知道如何清除这个 char[] 和你在图像上看到的那么大的字符串。有什么新想法吗?不增加内存真的不行吗?

【问题讨论】:

  • 平面文件的大小(以 KB 为单位)是多少?
  • 你说的是 400k lines 的 csv 吗?如果你这样做了,并且每行在“pedido”中变成大约 10 个字符串,那么 500 万个字符串实例似乎很合理。
  • @ViniciusMartin 您设置了Campo = null,但在将该数组中的字符串添加到pedido 之前。保留这些引用。
  • @AndréStannek 其中两个字符串变成了浮点数,所以它是 12-13 个字符串,但仍然非常合理。
  • 如果您不能/不想花费那么多内存,您应该考虑一下同时需要多少个“pedidos”。也许您可以一个接一个地读取+处理,而不是读取 400k 个对象然后一次性处理所有对象。

标签: java string char out-of-memory heap-memory


【解决方案1】:

正如已经在 cmets 中讨论的那样,主要因素是您的程序将所有内容同时放入内存中。这种设计本质上会限制您可以处理的文件的大小。

垃圾收集的工作方式是只收集垃圾。任何被另一个对象引用的对象都不是垃圾。因此,从“根”对象(当前在堆栈上声明为静态或局部变量的任何内容)开始,遵循引用。您的GerenciadorPedido 实例肯定是从main() 引用的。它引用了一个列表listaPedidos。该列表引用了(许多)Pedido 实例,每个实例都引用了许多 String 实例。这些对象在可以通过列表访问时都将保留在内存中。

设计您的程序以使其对可以处理的文件大小没有限制的方法是完全消除该列表。不要读取整个文件并返回列表(或其他集合)。而是实现Iterator。从 CSV 文件中读取一行,创建Pedido,返回它。当程序完成该程序后,读取下一行并创建下一个Pedido。那么在任何给定时间,您的内存中都只会有这些对象之一。

关于您当前算法的一些附加说明:

  • 每个String 对象在内部引用一个包含字符的char[]

  • ArrayList 在添加到大列表时具有非常差的内存使用特性。由于它由数组支持,为了增加新元素,它必须创建一个比当前数组更大的全新数组,然后复制所有引用。在此过程中,它将使用所需内存的两倍。列表越大,这也会变得越来越慢。

    • 一种解决方案是告诉 ArrayList 您需要它有多大,这样您就可以避免调整大小。这仅在您真正知道需要多大时才适用。如果您需要 100 个元素:new ArrayList&lt;&gt;(100)

    • 另一种解决方案是使用不同的数据结构。 LinkedList 更适合一次添加一个元素,因为它不需要分配和复制整个数组。

  • 每次调用.replaceAll() 都会为新的String 对象创建一个新的char[]。由于您将先前的String 对象孤立,因此它将被垃圾收集。请注意这种分配需求。

  • 每个字符串连接(例如newLinha + " "Campo[9] + Campo[10])将创建一个新的StringBuilder 对象,附加两个字符串,然后创建一个新的String 对象。同样,当对大量数据重复时,这可能会产生影响。

  • 一般来说,您永远不需要致电System.gc()。调用它是可以的,但是系统会在需要内存的时候执行垃圾回收。

另外一点:当数据包含您不期望的字符时,您解析 CSV 的方法将失败。特别是如果任何字段包含逗号。我建议使用现有的 CSV 解析库作为正确处理 CSV 整个定义的简单解决方案。 (我有使用opencsv的成功经验)

【讨论】:

    猜你喜欢
    • 2016-04-10
    • 2020-09-25
    • 2013-03-31
    • 1970-01-01
    • 2010-12-08
    • 2015-05-14
    相关资源
    最近更新 更多