Android的SSLServerSocket导致App,OOM中的本机内存增加

bot*_*oto 17 android out-of-memory

背景

我正在开发一个Android应用程序,它提供了一个简单的HTTP/HTTPS服务器.如果配置了HTTPS服务,则在每个连接上都会观察到增加的本机内存使用量,这最终会导致应用程序崩溃(oom),而使用HTTP配置会使本机内存使用量保持相对恒定.应用程序的Java VM在两种配置中保持相对稳定.

该应用程序提供一个HTML页面,其中包含一个定期轮询的javascript(每秒一次json轮询),因此使用HTTPS配置调用应用程序页面并保持页面打开几个小时将导致提到的内存不足,因为增加本机内存使用量.我测试了许多在互联网上找到的SSLServerSocket和SSLContext配置,但没有运气.

我在各种Android设备和各种Android版本上观察到相同的问题,从2.2到4.3开始.

处理客户端请求的代码对于HTTP/HTTPS配置都是相同的.两种配置的唯一区别是服务器套接字的设置.而在HTTP服务器套接字的情况下,一行类似于这个"ServerSocket serversocket = new ServerSocket(myport);" 做这项工作,在HTTPS服务器设置的情况下,采用设置SSLContext的常规步骤 - 即设置密钥管理器并初始化SSLContext.现在,我使用默认的TrustManager.

需要你的建议

有人知道使用OpenSSL的Android默认TLS提供程序中的任何内存泄漏问题吗?我应该考虑一些特殊的东西来避免本机内存中的泄漏吗?任何提示都受到高度赞赏.

更新:我还尝试了两个TLS提供程序:OpenSSL和JSSE,方法是在SSLContext.getInstance("TLS",providerName)中显式提供提供程序名称.但这并没有改变任何事情.

这是一个代码块,用于演示该问题.只需创建一个示例应用程序,将其放入主活动的onCreate底部,然后构建并运行应用程序.确保您的Wifi已开启并按以下地址调用HTML页面:

https://android device IP:9090
Run Code Online (Sandbox Code Playgroud)

然后观看adb日志,过一会儿你会看到本机内存开始增加.


new Thread(new Runnable() {

public void run() {

final int PORT = 9090; SSLContext sslContext = SSLContext.getInstance( "TLS" ); // JSSE and OpenSSL providers behave the same way KeyManagerFactory kmf = KeyManagerFactory.getInstance( KeyManagerFactory.getDefaultAlgorithm() ); KeyStore ks = KeyStore.getInstance( KeyStore.getDefaultType() ); char[] password = KEYSTORE_PW.toCharArray(); // we assume the keystore is in the app assets InputStream sslKeyStore = getApplicationContext().getResources().openRawResource( R.raw.keystore ); ks.load( sslKeyStore, null ); sslKeyStore.close(); kmf.init( ks, password ); sslContext.init( kmf.getKeyManagers(), null, new SecureRandom() ); ServerSocketFactory ssf = sslContext.getServerSocketFactory(); sslContext.getServerSessionContext().setSessionTimeout(5); try { SSLServerSocket serversocket = ( SSLServerSocket )ssf.createServerSocket(PORT); // alternatively, the plain server socket can be created here //ServerSocket serversocket = new ServerSocket(9090); serversocket.setReceiveBufferSize( 8192 ); int num = 0; long lastnatmem = 0, natmemtotalincrease = 0; while (true) { try { Socket soc = (Socket) serversocket.accept(); Log.i(TAG, "client connected (" + num++ + ")"); soc.setSoTimeout(2000); try { SSLSession session = ((SSLSocket)soc).getSession(); boolean valid = session.isValid(); Log.d(TAG, "session valid: " + valid); OutputStream os = null; InputStream is = null; try { os = soc.getOutputStream(); // just read the complete request from client is = soc.getInputStream(); int c = 0; String itext = ""; while ( (c = is.read() ) > 0 ) { itext += (char)c; if (itext.contains("\r\n\r\n")) // end of request detection break; } //Log.e(TAG, " req: " + itext); } catch (SocketTimeoutException e) { // this can occasionally happen (handshake timeout) Log.d(TAG, "socket timeout: " + e.getMessage()); if (os != null) os.close(); if (is != null) is.close(); soc.close(); continue; } long natmem = Debug.getNativeHeapSize(); long diff = 0; if (lastnatmem != 0) { diff = natmem - lastnatmem; natmemtotalincrease += diff; } lastnatmem = natmem; Log.i(TAG, " answer the request, native memory in use: " + natmem / 1024 + ", diff: " + diff / 1024 + ", total increase: " + natmemtotalincrease / 1024); String html = "<!DOCTYPE html><html><head>"; html += "<script type='text/javascript'>"; html += "function poll() { request(); window.setTimeout(poll, 1000);}\n"; html += "function request() { var xmlHttp = new XMLHttpRequest(); xmlHttp.open( \"GET\", \"/\", false ); xmlHttp.send( null ); return xmlHttp.responseText; }"; html += "</script>"; html += "</head><body onload=\"poll()\"><p>Refresh the site to see the inreasing native memory when using HTTPS: " + natmem + " </p></body></html> "; byte[] buffer = html.getBytes("UTF-8"); PrintWriter pw = new PrintWriter( os ); pw.print("HTTP/1.0 200 OK \r\n"); pw.print("Content-Type: text/html\r\n"); pw.print("Content-Length: " + buffer.length + "\r\n"); pw.print("\r\n"); pw.flush(); os.write(buffer); os.flush(); os.close(); } catch (IOException e) { e.printStackTrace(); } soc.close(); } catch (IOException e) { e.printStackTrace(); } } } catch (SocketException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } } }).start();
Run Code Online (Sandbox Code Playgroud)

- 编辑 -

我已经为eClipse上传了一个名为SSLTest的示例应用程序项目,它演示了这个问题:

http://code.google.com/p/android/issues/detail?id=59536

- 更新 -

好消息:今天发现了上面报告的Android问题,并提交了正确的提交内容以解决内存泄漏问题.有关详细信息,请参阅上面的链接.

Dav*_*nty 0

显式关闭输入流有帮助吗?在示例代码中,输入流似乎仅在出现 SocketTimeoutException 异常的情况下才会关闭。

- 编辑 -

您可以将 run() 重命名为 run2() 并将 while 循环移动到 run() 中并将其从 run2() 中删除,看看这是否有区别?这不是一个解决方案,但会告诉您当引用被删除时,是否有任何长期存在的对象释放内存。