错误:java.lang.OutOfMemoryError:Java堆空间

时间:2015-10-20 15:46:39

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

我有这个" java.lang.OutOfMemoryError:Java堆空间"我读到并明白我可以使用-Xmx1024m增加记忆力。但我认为在我的代码中我可以改变一些东西,这个错误不再发生了。

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

enter image description here

在图像中你可以看到对象" Pedidos"不是那么大,我有另一个对象" Enderecos"因为我在对象完成之前有错误,所以大小相同但不完整。

重点是:

  • 我有2个类搜索一个大的csv文件(每个400.000个值),我将显示代码。我尝试使用垃圾收集器,将变量设置为null,但是无法正常工作,有人可以帮我吗?以下是班级#34; 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 []和字符串,就像你在图像上看到的一样。有什么新想法吗?如果不增加记忆真的不可能吗?

1 个答案:

答案 0 :(得分:2)

正如评论中已经讨论的那样,主要因素是你的程序同时将所有内容放在内存中。该设计将固有地限制您可以处理的文件的大小。

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

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

有关您当前算法的一些其他说明:

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

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

    • 一种解决方案是告诉ArrayList你需要它多大,这样你就可以避免调整大小。这仅适用于您实际知道需要多大的情况。如果您需要100个元素:new ArrayList<>(100)

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

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

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

  • 一般来说,您应该永远不需要致电System.gc()。可以调用它,但只要需要内存,系统就会执行垃圾收集。

一个附加说明:当数据包含您不期望的字符时,解析CSV的方法将失败。特别是如果任何字段包含逗号。我建议使用现有的CSV解析库来获得正确处理CSV整个定义的简单解决方案。 (我有使用opencsv

的成功经验