Android因堆大小增加而导致内存不足错误

14

我遇到了内存不足错误。我正在开发一个实时聊天应用程序,它一开始运行良好,但在设备上运行 1 到 2 小时后堆大小会增加,当达到 16MB 时应用开始出现卡顿,并在一段时间后崩溃并显示 由于堆大小而导致内存不足,因为最终的堆大小比分配的大小要大。

我正在 HTC Explorer 上测试我的应用程序。在我的应用程序中,大多数活动都使用后台线程,我使用 Asnyc Task 实现。

我遇到了以下错误。

04-30 16:53:14.658: E/AndroidRuntime(5707): FATAL EXCEPTION: MagentoBackground
04-30 16:53:14.658: E/AndroidRuntime(5707): java.lang.OutOfMemoryError: (Heap Size=20167KB, Allocated=16063KB, Bitmap Size=355KB)
04-30 16:53:14.658: E/AndroidRuntime(5707):     at org.apache.http.util.ByteArrayBuffer.<init>(ByteArrayBuffer.java:53)
04-30 16:53:14.658: E/AndroidRuntime(5707):     at org.apache.http.impl.io.AbstractSessionInputBuffer.init(AbstractSessionInputBuffer.java:82)
04-30 16:53:14.658: E/AndroidRuntime(5707):     at org.apache.http.impl.io.SocketInputBuffer.<init>(SocketInputBuffer.java:98)
04-30 16:53:14.658: E/AndroidRuntime(5707):     at org.apache.http.impl.SocketHttpClientConnection.createSessionInputBuffer(SocketHttpClientConnection.java:83)
04-30 16:53:14.658: E/AndroidRuntime(5707):     at org.apache.http.impl.conn.DefaultClientConnection.createSessionInputBuffer(DefaultClientConnection.java:170)
04-30 16:53:14.658: E/AndroidRuntime(5707):     at org.apache.http.impl.SocketHttpClientConnection.bind(SocketHttpClientConnection.java:106)
04-30 16:53:14.658: E/AndroidRuntime(5707):     at org.apache.http.impl.conn.DefaultClientConnection.openCompleted(DefaultClientConnection.java:129)
04-30 16:53:14.658: E/AndroidRuntime(5707):     at org.apache.http.impl.conn.DefaultClientConnectionOperator.openConnection(DefaultClientConnectionOperator.java:173)
04-30 16:53:14.658: E/AndroidRuntime(5707):     at org.apache.http.impl.conn.AbstractPoolEntry.open(AbstractPoolEntry.java:164)
04-30 16:53:14.658: E/AndroidRuntime(5707):     at org.apache.http.impl.conn.AbstractPooledConnAdapter.open(AbstractPooledConnAdapter.java:119)
04-30 16:53:14.658: E/AndroidRuntime(5707):     at org.apache.http.impl.client.DefaultRequestDirector.execute(DefaultRequestDirector.java:359)
04-30 16:53:14.658: E/AndroidRuntime(5707):     at org.apache.http.impl.client.AbstractHttpClient.execute(AbstractHttpClient.java:555)
04-30 16:53:14.658: E/AndroidRuntime(5707):     at org.apache.http.impl.client.AbstractHttpClient.execute(AbstractHttpClient.java:487)
04-30 16:53:14.658: E/AndroidRuntime(5707):     at com.live2support.CustomHttpClient.executeHttpPost1(CustomHttpClient.java:163)

堆大小有限制吗?我该如何解决我的问题?


1
发一些代码。我猜你在自定义列表中使用了大型位图。 - Venky
你真的需要在运行时获取所有聊天历史记录吗?或者你是将这些聊天历史记录维护在数组、集合对象中,或者你的列表视图太大了。你可以使用动态增长的列表视图。 - Animesh Sinha
在com.live2support.CustomHttpClient.executeHttpPost1(CustomHttpClient.java:163)中--你正在做一些每次占用大量内存的事情..尝试在使用后立即释放该内存..无论何时,对象、arraylist、位图都应该在使用后立即从内存中清除..一旦使用后赋值为null并调用System.gc();来向垃圾收集器指示一个标志。 - MKJParekh
是的,我在各处调用System.gc();。 - nikki
Anieeh,我并不总是需要聊天记录,但有时候我需要展示聊天历史。 - nikki
显示剩余2条评论
5个回答

13

你的问题分为两部分:

1)如何确定测试设备的堆大小?

2)为什么我的应用程序超出了堆大小?

关于问题1,你可以直接在代码中调用以下方法来确定测试设备的堆大小:

Runtime.getRuntime().maxMemory();

查看此帖子获取有关该方法的附加信息,以及各种设备上可用的一些示例堆大小。

此外,如果你正在运行已root的设备,则可以通过接口直接设置(和检查)堆大小。例如,在CyanogenMod的各个版本的Android中,从"设置"菜单中,你可以选择"CyanogenMod设置",然后选择"性能",再选择"VM堆大小",并直接查看(和更改)设备的堆大小。请注意,将堆大小设置得太小可能会使你的设备行为异常或更糟。

关于问题2:你没有提供足够的信息来诊断你的具体问题,而且在任何情况下,进行二手诊断都很困难。解决此问题的最佳方法(并在此过程中学习一些持久价值的东西)是熟悉Android中一些非常强大的内存分析工具(其中一些也集成在Eclipse IDE中)。我使用Eclipse中的这些工具,因此下面将对此进行描述。

首先,请确保你的Eclipse版本是最新的,通过安装最新版本的Eclipse(例如Indigo)来更新。

接下来,在Eclipse中选择Help/Install New Software,然后点击顶部的下拉菜单并选择

"Indigo - http://download.eclipse.org/releases/indigo"

接下来,点击General Purpose Tools类别旁边的加号图标,选择Memory Analyzer以及Memory Analyzer (Charts) [可选]并安装这些工具。

然后,选择Window/Preferences,然后选择Android/DDMS,将HPROF操作设置为“在Eclipse中打开”。这将导致您从DDMS生成的任何HPROF堆转储文件都符合Eclipse的适当格式,并且还会自动在刚刚安装的Eclipse Memory Analyzer中打开。

现在,通过选择Window/Open Perspective/Other/DDMS打开DDMS。在左侧选择设备图标(看起来像手机),并将生成的窗口拖动到容易查看的位置。

确保您的设备通过USB连接到PC,并且您的应用正在运行。

在您刚刚创建的设备选项卡中,选择正在运行的应用程序进程。运行应用程序,直到它占用足够的内存,以知道它已经泄漏,但不要超过崩溃阈值。现在,在设备选项卡中点击Dump HPROF File图标。稍等片刻后,您将有多个报告选项可供选择。尝试Leak Suspects Report以开始分析。此报告将在Memory Analysis Tool中打开。它会告诉您应用程序正在使用内存的位置。检查各种对象并查看它们是否相对于您预计的数据量而言过大,如果是,则可能表示有泄漏现象。

在此是一个很好的教程,详细描述如何使用DDMS和Memory Analyzer Tool生成和分析堆。

回到DDMS(或Eclipse中的DDMS Perspective),当设备连接时,可以选择Allocation Tracker选项卡,然后从设备选项卡中选择您的设备,并从该设备的列表中选择应用程序的进程。然后,在Allocation Tracker选项卡上,单击Start Tracking按钮,运行您认为存在内存泄漏问题的相关操作,然后单击Get Allocations按钮,最后再选择Stop Tracking按钮。

这将显示您跟踪期间发生的所有内存分配情况(存储量有限)。点击其中任何一个将带您进入分配时的堆栈,并点击任何堆栈转储中的任何部分,将带您进入涉及分配的源代码。

这些工具应该能够帮助您了解应用程序内存泄漏的原因。


7
看起来像是典型的内存泄漏问题。你说你在连接中使用了AsyncTask。如果你不知道如何正确使用它,在配置更改(例如设备旋转)时很容易因为AsyncTask泄漏上下文。
首先,我强烈建议你观看这个视频:http://www.youtube.com/watch?v=_CruQY55HOk 为了检查是否存在内存泄漏,请旋转您的设备并检查垃圾收集器的行为。您应该会看到类似于这样的信息:GC_... freed 211K, 71% free 300K/1024K, external 0K/0K, paused 1ms+1ms出现在LogCat中,每次旋转时都会有。注意这部分内容:300K/1024K的变化。如果您没有内存泄漏,第一部分应该会增长并在几次GC之后变小。如果您有内存泄漏,它将不断增长,直至OOM错误发生。
如果您确认您存在内存泄漏,您应该安装Eclipse的MAT,学习如何使用它(使用前述的视频),并找出导致内存泄漏的原因。
我个人认为是AsyncTask实现不良——您是否从已销毁的Activity中分离它并附加到新的Activity上?如果没有,请开始这样做(CommonsWare有一个很好的示例),或切换到AsyncTaskLoader,它将自动处理此问题,并且通常是AsyncTask的绝佳替代品(不仅限于加载内容)。

我遇到了一个奇怪的问题,即只有在调试器中启动应用程序时才会出现OOM错误。如果使用启动器图标正常启动,则不会发生内存泄漏:https://dev59.com/FmPVa4cB1Zd3GeqP2RVs - Michał Klimczak

5
只需在应用程序标签中添加android:largeHeap="true"即可,在manifests文件中。

3
虽然这段代码或许能够回答问题,但是提供额外的上下文说明它是如何解决问题的,或者为什么能够解决问题,会提高回答的长期价值。 - mech

1

您的堆大小限制取决于设备。在2.x设备上,我预计您的限制约为20或32 MB。有关堆大小的更多信息,请参见不同手机/设备和操作系统版本上的Android堆大小

从您的堆栈跟踪中,看起来com.live2support.CustomHttpClient.executeHttpPost1()是您问题的核心。


通常情况下,我会按照以下顺序尝试:1. 对代码进行视觉检查。此外,您也可以将您的函数添加到 Stack Overflow 的问题中,以便让其他人进行查看。2. 使用源代码级调试器逐步执行代码。3. 如果第二个方法行不通,就可以使用 DDMS。http://developer.android.com/guide/developing/debugging/ddms.html - Sparky

1

根据您的Android操作系统,有两种方法可以实现此目标。

  1. 您可以在Android清单文件的应用程序标签中使用android:largeHeap="true"来请求更大的堆大小,但这不适用于任何早期的Honeycomb设备。
  2. 在2.3之前的设备上,您可以使用VMRuntime类,但这在Gingerbread及以上版本上无法使用。请参阅下面的说明。
VMRuntime.getRuntime().setMinimumHeapSize(BIGGER_SIZE);

在设置HeapSize之前,请确保您输入的大小不会影响其他应用程序或操作系统功能。在进行设置之前,只需检查您的应用程序占用了多少空间,然后根据需要设置大小即可。不要使用太多的内存,否则可能会影响其他应用程序。
参考:http://dwij.co.in/increase-heap-size-of-android-application

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接