0

我有一个自己编写的 Java 应用程序——一个小邮件监视器。它与一个 MySQL 数据库一起工作,该数据库有一个定期在其他地方填充的表。它查看表格并在记录出现在表格中时发送邮件。

我的问题是,应用程序泄漏内存。我很确定它没有,因为我使用的所有东西的范围似乎都消失了,使所有使用的对象都可以垃圾回收。但过了一会儿(取决于我通过的 -Xmx),应用程序停止并出现 OutOfHeapSpace 错误。

我无法发布整个代码,因为它不再是我的了,但我尝试使用伪代码重新创建它。

Main:
  Startup
  Create .lock file (FileChannel)
  Instantiate Main Class

Constructor Main:
  Class.ForName for the MySQL driver
  Read properties file (settings)
  Create connection object (MySQL)
  Fetch unsent mail ids (ArrayList)
  while(true)
    while have more mail ids
      new Thread(Top Mail ID, MySQL Connection object, Sleep Time, Blacklist);
    end while
    if have no more mail ids in ArrayList:
      sleep for a number of seconds (usually 300)
    end if
  end while

Constructor Thread:
  Prepare Statement
  New Thread(this).start();
  Sleep

Thread run():
  Select Record by passed Mail ID
  Extract everything (Sender, Receiver, Subject etc.)
  Check Blacklist, return if matched
  Extract Attachments as blobs

到目前为止我已经尝试过:

jvisualvm 向我展示了内存如何随时间变化。我看到的是堆中的一条锯齿线:分配和收集内存定期发生,但是,在收集之后分配的内存总是比上次收集后多一点。线程的数量似乎很好,它总是下降到标准数量。

jvisualvm 中的信息量对我来说太多了。那里列出了我无法识别的线程,我创建的线程没有列为我的类,因此很难确定究竟什么是“我的”代码。

任何人都可以在我的伪代码中识别出任何常见的多线程错误,或者推荐任何可以让我更容易确定泄漏的工具吗?

谢谢你。

编辑1:通过传递给所有线程的连接对象的数据访问在其自身上同步。

编辑 2:我检查了无法发送的邮件数量(因为这些邮件保留在数据库中),大约有 10 个。

编辑3:我找到了Eclipse Memory Analyzer,安装了它,它暗示了这个问题。似乎在保留一个连接对象的同时使用 PreparedStatements 会保留在其中运行过该连接的所有 PreparedStatements 的 HashMap,因此将所有选择的数据添加到其中。我依靠 PreparedStatement 超出范围并被收集。如果这能解决问题,我将重写并尝试。

4

2 回答 2

1

从您的伪代码来看,您似乎正在为每封要发送的电子邮件创建一个线程,这似乎并不那么有效。我知道您提到线程数是“正常的”,但代码似乎另有说明。您是否可以尝试使用线程池,这样您的线程工作人员数量有限,并通过某种作业队列将工作传递给他们。

你知道是否有一些错误可能导致线程无法完成或让它们“挂起”?这可能是内存泄漏,因此请查看您的错误处理代码。

过去我使用过jprofiler并取得了一些成功,也许它会是一个有用的选择。

于 2012-06-11T09:27:50.047 回答
0

如果这对任何人都感兴趣,我修复了内存问题。

该应用程序实际上并没有泄漏内存。当我传递相同的连接对象时,它似乎将针对它运行的每个准备好的语句的结果保存在一个大的哈希图中。因此,内存使用量不断上升。我通过使用 jvisualvm 创建的堆转储并加载到 Eclipse 内存分析器中发现了这一点。

我重写了应用程序以使用线程池(缓存),为每个线程打开一个专用连接,并在每个线程结束时关闭连接。

于 2012-06-15T11:53:02.787 回答