我有这个" java.lang.OutOfMemoryError:Java堆空间"我读到并明白我可以使用-Xmx1024m增加记忆力。但我认为在我的代码中我可以改变一些东西,这个错误不再发生了。
首先,这是来自VisualVM的关于我记忆的图像:
在图像中你可以看到对象" 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 []和字符串,就像你在图像上看到的一样。有什么新想法吗?如果不增加记忆真的不可能吗?
答案 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)
的成功经验