4G/LTE 上的 Android HttpClient OOM (HTC Thunderbolt)
我收到了一些用户的报告,称尝试在 Verizon 的 4G/LTE 上使用我的应用程序时发生崩溃。
查看堆栈跟踪,看起来 Android 的 HttpClient.execute() 实现抛出了 OOM。这种情况仅发生在 4G/LTE 设备上,特别是 HTC Thunderbolt,并且仅在使用 4G/LTE 时才会发生。 WiFi、3G、UMTS 都可以。在 Sprint 的 WiMax 4G 上也能正常工作。
两个问题:
引起 Android 开发人员对此关注的最佳方式是什么?还有比举报 http://code.google.com/p/android/issues 更好的选择吗?
关于如何解决这个问题有什么想法吗?我自己没有 4G 设备,而且我无法在模拟器中发生这种情况,所以我需要在这里做出一些有根据的猜测。我可以尝试捕获代码中的 OOM 并尝试清理并强制 GC,但我不确定这是否是一个好主意。意见或其他建议?
这是我的代码正在做的事情:
HttpParams params = this.getHttpParams(); // returns params
ClientConnectionManager cm = new ThreadSafeClientConnManager(params, this.getHttpSchemeRegistry() );
DefaultHttpClient httpClient = new DefaultHttpClient( cm, params );
HttpResponse response = null;
request = new HttpGet( url );
try {
response = httpClient.execute(request); // <-- OOM on 4G/LTE. OK otherwise
int statusCode = response.getStatusLine().getStatusCode();
Log.i("fetcher", "execute returned, http status " + statusCode );
...
这是崩溃的堆栈跟踪:
E/dalvikvm-heap(11639):内存不足 分配 2055696 字节。 I/dalvikvm(11639):“Thread-16”prio=5 tid=9 可运行 I/dalvikvm(11639): | 组=“主” sCount=0 dsCount=0 s=N obj=0x48563070 自身=0x3c4340 我/dalvikvm(11639): |系统Tid=11682 好=0 计划=0/0 cgrp=默认 句柄=3948760 I/dalvikvm(11639): | schedstat=( 208709711 74005130 214 )
I/dalvikvm(11639):位于 org.apache.http.impl.io.AbstractSessionInputBuffer.init(AbstractSessionInputBuffer.java:~79) 我/dalvikvm(11639): 在 org.apache.http.impl.io.SocketInputBuffer.(SocketInputBuffer.java:93) 我/dalvikvm(11639): 在 org.apache.http.impl.SocketHttpClientConnection.createSessionInputBuffer(SocketHttpClientConnection.java:83) 我/dalvikvm(11639): 在 org.apache.http.impl.conn.DefaultClientConnection.createSessionInputBuffer(DefaultClientConnection.java:170) 我/dalvikvm(11639): 在 org.apache.http.impl.SocketHttpClientConnection.bind(SocketHttpClientConnection.java:106) 我/dalvikvm(11639): 在 org.apache.http.impl.conn.DefaultClientConnection.openCompleted(DefaultClientConnection.java:129) 我/dalvikvm(11639): 在 org.apache.http.impl.conn.DefaultClientConnectionOperator.openConnection(DefaultClientConnectionOperator.java:173) 我/dalvikvm(11639): 在 org.apache.http.impl.conn.AbstractPoolEntry.open(AbstractPoolEntry.java:164) 我/dalvikvm(11639): 在 org.apache.http.impl.conn.AbstractPooledConnAdapter.open(AbstractPooledConnAdapter.java:119) 我/dalvikvm(11639): 在 org.apache.http.impl.client.DefaultRequestDirector.execute(DefaultRequestDirector.java:348) 我/dalvikvm(11639): 在 org.apache.http.impl.client.AbstractHttpClient.execute(AbstractHttpClient.java:555) 我/dalvikvm(11639): 在 org.apache.http.impl.client.AbstractHttpClient.execute(AbstractHttpClient.java:487) 我/dalvikvm(11639): 在 org.apache.http.impl.client.AbstractHttpClient.execute(AbstractHttpClient.java:465) 我/dalvikvm(11639): 在 com.myapplication.Fetcher.trySourceFetch(Fetcher.java:205) 我/dalvikvm(11639): 在 com.myapplication.Fetcher.run(Fetcher.java:298) 我/dalvikvm(11639): 在 java.lang.Thread.run(Thread.java:1102) I/dalvikvm(11639): E/dalvikvm(11639): 内存不足:堆大小=24171KB, 分配=23142KB,位图大小=59KB, 限制=21884KB E/dalvikvm(11639):额外 信息:足迹=24327KB,允许 占用空间=24519KB,修剪=348KB W/dalvikvm(11639): threadid=9: 线程 退出时未捕获异常 (组=0x40025b38)
I have gotten some reports from users of crashes when try use my application on Verizon's 4G/LTE.
Looking at the stack trace, it looks like Android's HttpClient.execute() implementation is throwing an OOM. This happens only on 4G/LTE devices, specifically HTC Thunderbolt, and only when on 4G/LTE. WiFi, 3G, UMTS are OK. Also works fine on Sprint's WiMax 4G stuff works fine.
Two questions:
What's the best way to get the attention of Android devs about this? Any better options than reporting on http://code.google.com/p/android/issues?
Any ideas on how I can work around this? I don't have a 4G device myself and I can't get this happen in the emulator so I need to make some educated guesses here. I can try to catch the OOM in my code and attempt to cleanup and force GC, but I'm not sure if that's a good idea. Comments or other suggestions?
Here's what my code's doing:
HttpParams params = this.getHttpParams(); // returns params
ClientConnectionManager cm = new ThreadSafeClientConnManager(params, this.getHttpSchemeRegistry() );
DefaultHttpClient httpClient = new DefaultHttpClient( cm, params );
HttpResponse response = null;
request = new HttpGet( url );
try {
response = httpClient.execute(request); // <-- OOM on 4G/LTE. OK otherwise
int statusCode = response.getStatusLine().getStatusCode();
Log.i("fetcher", "execute returned, http status " + statusCode );
...
Here's the crashing stack trace:
E/dalvikvm-heap(11639): Out of memory
on a 2055696-byte allocation.
I/dalvikvm(11639): "Thread-16" prio=5
tid=9 RUNNABLE I/dalvikvm(11639): |
group="main" sCount=0 dsCount=0 s=N
obj=0x48563070 self=0x3c4340
I/dalvikvm(11639): | sysTid=11682
nice=0 sched=0/0 cgrp=default
handle=3948760 I/dalvikvm(11639): |
schedstat=( 208709711 74005130 214 )I/dalvikvm(11639): at
org.apache.http.impl.io.AbstractSessionInputBuffer.init(AbstractSessionInputBuffer.java:~79)
I/dalvikvm(11639): at
org.apache.http.impl.io.SocketInputBuffer.(SocketInputBuffer.java:93)
I/dalvikvm(11639): at
org.apache.http.impl.SocketHttpClientConnection.createSessionInputBuffer(SocketHttpClientConnection.java:83)
I/dalvikvm(11639): at
org.apache.http.impl.conn.DefaultClientConnection.createSessionInputBuffer(DefaultClientConnection.java:170)
I/dalvikvm(11639): at
org.apache.http.impl.SocketHttpClientConnection.bind(SocketHttpClientConnection.java:106)
I/dalvikvm(11639): at
org.apache.http.impl.conn.DefaultClientConnection.openCompleted(DefaultClientConnection.java:129)
I/dalvikvm(11639): at
org.apache.http.impl.conn.DefaultClientConnectionOperator.openConnection(DefaultClientConnectionOperator.java:173)
I/dalvikvm(11639): at
org.apache.http.impl.conn.AbstractPoolEntry.open(AbstractPoolEntry.java:164)
I/dalvikvm(11639): at
org.apache.http.impl.conn.AbstractPooledConnAdapter.open(AbstractPooledConnAdapter.java:119)
I/dalvikvm(11639): at
org.apache.http.impl.client.DefaultRequestDirector.execute(DefaultRequestDirector.java:348)
I/dalvikvm(11639): at
org.apache.http.impl.client.AbstractHttpClient.execute(AbstractHttpClient.java:555)
I/dalvikvm(11639): at
org.apache.http.impl.client.AbstractHttpClient.execute(AbstractHttpClient.java:487)
I/dalvikvm(11639): at
org.apache.http.impl.client.AbstractHttpClient.execute(AbstractHttpClient.java:465)
I/dalvikvm(11639): at
com.myapplication.Fetcher.trySourceFetch(Fetcher.java:205)
I/dalvikvm(11639): at
com.myapplication.Fetcher.run(Fetcher.java:298)
I/dalvikvm(11639): at
java.lang.Thread.run(Thread.java:1102)
I/dalvikvm(11639): E/dalvikvm(11639):
Out of memory: Heap Size=24171KB,
Allocated=23142KB, Bitmap Size=59KB,
Limit=21884KB E/dalvikvm(11639): Extra
info: Footprint=24327KB, Allowed
Footprint=24519KB, Trimmed=348KB
W/dalvikvm(11639): threadid=9: thread
exiting with uncaught exception
(group=0x40025b38)
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(3)
您在该问题上的堆栈跟踪并未表明这一点。当然,您没有提供有关该问题的完整堆栈跟踪。
这是一个纯粹的 Android bug 的可能性很小,但不是零。
以下是一些其他可能性,排名不分先后:
execute() 本身没有问题,但您只是耗尽了内存,并且您遇到的堆栈跟踪是只是证明
execute()
正在给您的堆带来压力。问题出在 HTC 针对 Thunderbolt 对 Android 进行的一些修改,可能只在 LTE 网络上生效。
该问题在某种程度上是由 Verizon LTE 网络本身引起的(例如,他们的某些代理发回导致 HttpClient 出现问题的怪异信息)。
首先,我将使用现有工具(例如,转储 HPROF 并使用 Eclipse MAT 检查)来确认一般情况下不会出现 Thunderbolt/LTE 组合似乎被绊倒的内存泄漏。
接下来,我建议您想出一些方法来一致地重现错误。这可能是您现有的应用程序,需要遵循一系列步骤,也可能是一个专用应用程序(例如,记录触发 OOM 的 URL,然后创建一个仅执行该 HttpClient 请求的小型应用程序)。我希望 DeviceAnywhere 有 Thunderbolt,但它看起来不像。我会试探一下,看看是否能在这方面获得一些帮助。
在解决这个问题方面,作为权宜之计,您可以通过 android.os.Build 数据检测到您正在 Thunderbolt 上运行,也许通过 ConnectivityManager 检测到您正在 LTE 上运行 (我猜测 LTE 会列为 WiMAX,但这只是猜测),并警告用户该组合存在的问题。
除此之外,您可以尝试稍微改变一下 HttpClient 的用法,看看是否有效果,例如:
AndroidHttpClient
直接替换很抱歉,我在这里没有“神奇的子弹”答案。
更新
现在我有了完整的堆栈跟踪,查看源代码......有点具有启发性。
问题似乎是:
返回触发 OOM 的 2MB 左右的值。这是一个非常大的缓冲区,特别是对于 Dalvik GC 引擎来说,它可能会产生碎片(是的,又是这个词)。
这里的
params
是HttpParams
。您似乎是通过 getHttpParams() 自己创建的。例如,AndroidHttpClient
将其设置为 8192:如果您自己设置套接字缓冲区大小,请尝试减小它。如果没有,请尝试将其设置为 8192,看看是否有帮助。
That is not indicated by the stack trace you have on the issue. Of course, you didn't provide the whole stack trace on the issue.
The odds of this being a pure Android bug are small, though not zero.
Here are some other possibilities, in no particular order:
There is no problem with
execute()
per se, but that you are simply running out of memory, and the stack traces you have encountered are simply demonstrating thatexecute()
is stressing your heap.The problem is in some modifications that HTC made to Android for the Thunderbolt, possibly only taking effect when on the LTE network.
The problem is somehow caused by the Verizon LTE network itself (e.g., some proxy of theirs sending back screwball information that is causing HttpClient to have a conniption).
First, I'd use existing tools (e.g., dumping HPROF and examining with Eclipse MAT) to confirm that you don't have a memory leak in general that the Thunderbolt/LTE combo just seems to be tripping over.
Next, I recommend that you come up with some way to consistently reproduce the error. That could be your existing app with a series of steps to follow, or it could be a dedicated app (e.g., log the URL that triggers the OOM, then create a tiny app that just does that HttpClient request). I wish DeviceAnywhere had a Thunderbolt, but it doesn't look like it. I'll put some feelers out and see if I can get some help on that front.
In terms of working around it, as a stopgap, you can detect that you're running on a Thunderbolt via
android.os.Build
data, and perhaps that you're on LTE viaConnectivityManager
(I'm guessing LTE would list as WiMAX, but that's just a guess), and warn users about the problems with that combo.Beyond that, you can try changing up your HttpClient usage a bit and see if it has an effect, such as:
AndroidHttpClient
a shot as a drop-in replacementThreadSafeClientConnManager
I'm sorry that I don't have a "magic bullet" answer for you here.
UPDATE
Now that I have the full stack trace, looking through the source code is...illuminating, somewhat.
The problem appears to be that:
is returning that 2MB or so value that is triggering the OOM. That's an awfully big buffer, particularly for the Dalvik GC engine, which can get fragmented (yes, there's that word again).
params
here is theHttpParams
. You seem to be creating those yourself viagetHttpParams()
. For example,AndroidHttpClient
sets that to 8192:If you are setting the socket buffer size yourself, try reducing it. If not, try setting it to 8192 and see if that helps.
这是修复方法:https://review.source.android.com/22852
同时, URLConnection 是免疫的。只有 HttpClient 有这个问题。
如果您是想要测试此类故障的开发人员,您可以使用“adb shell setprop”来设置,例如“net.tcp.buffersize.wifi”,以便最大读/写套接字缓冲区大小在您的设备已连接 wifi。类似下面的内容将是真正的压力测试:
正是这种配置更改引发了 HttpClient 错误。我不知道 Thunderbolt 上的确切值是多少,但拥有该设备的人可以使用“adb shell getprop | grep buffersize”找到。
here's the fix: https://review.source.android.com/22852
in the meantime, URLConnection is immune. it's only HttpClient that has this problem.
if you're a developer wanting to test this kind of failure, you can use "adb shell setprop" to set, say, "net.tcp.buffersize.wifi" so that the maximum read/write socket buffer sizes are huge when your device is on wifi. something like the following would be a real stress test:
it's this kind of configuration change that exercises the HttpClient bug. i don't know what the exact values on the Thunderbolt are, but someone with the device could find out using "adb shell getprop | grep buffersize".
也许这会有所帮助:
Maybe this will help: