使用inputStream.available()的Java/Android SDK内存泄漏
我编写了一个Java类,它有一个现有tcp连接池和一个在这些连接上循环的工作线程。在检查是否需要处理来自对等方的信息的函数中,它调用inputStream.available()。我在看记忆追踪器,它一直在上升。当我记录分配时,有几百个android.util.MutableInt对象分配在使用inputStream.available()的Java/Android SDK内存泄漏,java,android,performance,Java,Android,Performance,我编写了一个Java类,它有一个现有tcp连接池和一个在这些连接上循环的工作线程。在检查是否需要处理来自对等方的信息的函数中,它调用inputStream.available()。我在看记忆追踪器,它一直在上升。当我记录分配时,有几百个android.util.MutableInt对象分配在 at libcore.io.IoBridge.available(IoBridge.java:57) at java.net.PlainSocketImpl.available(PlainSocketImp
at libcore.io.IoBridge.available(IoBridge.java:57)
at java.net.PlainSocketImpl.available(PlainSocketImpl.java:128)
at java.net.PlainSocketImpl$PlainSocketInputStream.available(PlainSocketImpl.java:225)
at myClass
问题代码:
void someFunction() {
(new Thread(){
public void run() {
try {
while(true) {
for(int i=0; i<peers.size(); i++) {
peer = peers.get(i);
peer.doWorkListen();
peer.doWorkSend();
}
Thread.sleep(1);
}
catch (Exception e) {}
}).start();
}
void peer.doWorkListen() {
if(inputStream.available() > 0) {
//Read and process input
}
}
void someFunction(){
(新线程(){
公开募捐{
试一试{
while(true){
对于(int i=0;i 0){
//读取并处理输入
}
}
您遇到的不是内存泄漏,而是垃圾收集器无法跟上每秒创建的大量垃圾对象。*最终,当gc调用stop the world技术大规模删除不需要的对象时,您会发现应用程序暂时冻结
从您的代码中,我可以看到您正试图使用一个线程从多个流中轮询可用性。我认为您在这里创建的是Java/Android非阻塞I/O的一个非常基本且低效的实现
给定堆栈跟踪,我还假设您在本例中使用套接字。使用ServerSocketChannel
和SocketChannel
以及选择器
可以为您提供所需的功能。下面提供了一个仅使用选择器和SocketChannel的示例实现(如果这是服务器端代码,则应使用相同的逻辑实现ServerSocketChannel
)
public类Foo实现Runnable{
私人最终选择权;
私钥;
public Foo()引发IOException{
选择器=selector.open();
run=true;
}
公共无效注册表通道(SocketChannel通道)引发IOException{
信道配置阻塞(假);
//也可以选择使用选择键进行写入
通道寄存器(选择器,选择键,操作读取);
}
公共空间关闭(){
运行=错误;
selector.wakeup();
试一试{
selector.close();
}捕获(IOException忽略){}
}
公开募捐{
while(运行){
试一试{
int readyCount=selector.select();
//选择器被中断或手动唤醒
如果(readyCount==0){
//妥善处理
}否则{
迭代器iterKeys=selector.selectedKeys().Iterator();
while(iterKeys.hasNext()){
SelectionKey=iterKeys.next();
if(key.isReadable()){
SocketChannel chn=(SocketChannel)key.channel();
//在这里处理输入
}否则{
//我们对不可读的通道不感兴趣
}
//非常重要
iterKeys.remove();
}
}
}捕获(IOEX异常){
//句柄选择器异常
}
}
}
}
类Foo
充当您创建的线程的可运行线程,并以有效的方式多路复用SocketChannel
,阻止(与快速轮询相反)套接字,直到有一个可用。这不仅消除了大规模对象创建的问题,还消除了使用可用()
**这在不同的平台上可能不可靠。这种设计模式还有一个额外的好处,即可能比使用大量套接字的方法更快,因为它不必按顺序遍历套接字列表
查看和上的教程以及,以获得更深入的理解
*我只能跟踪
available()
到IoBridge.available(fd)
(我没有IoBridge
)的实现,因此无法完全排除内存泄漏的可能性,尽管可能性极低
**
available()
,如果非阻塞I/O确实是libcore中的内存泄漏,则可以避开该问题。是否可以提供可疑信息code@QuinnRoundy我已经添加了有问题代码的要点测试和睡眠,并在每个连接上使用一个读取线程。这是一种糟糕的技术。@EJP为什么这是一种糟糕的技术?真的没有这种情况是有利的吗?如果每个对等点都是严格独立的,我会采用传统的方法,但在这种情况下,如果它们在sin上运行,它会大大简化协作gle线程。我花了一段时间来学习新的范例和修改我的许多代码,但我已经让它正常工作,并且内存使用合理。谢谢!
public class Foo implements Runnable{
private final Selector selector;
private volatile boolean run;
public Foo() throws IOException{
selector = Selector.open();
run = true;
}
public void registerChannel(SocketChannel channel) throws IOException{
channel.configureBlocking(false);
// Optionally use a selection key for write as well
channel.register(selector, SelectionKey.OP_READ);
}
public void shutdown(){
run = false;
selector.wakeup();
try{
selector.close();
}catch(IOException ignore){}
}
public void run(){
while(run){
try{
int readyCount = selector.select();
// Selector was interrupted or manually woken up
if(readyCount == 0){
// handle appropriately
}else{
Iterator<SelectionKey> iterKeys = selector.selectedKeys().iterator();
while(iterKeys.hasNext()){
SelectionKey key = iterKeys.next();
if(key.isReadable()){
SocketChannel chn = (SocketChannel) key.channel();
// Process input here
}else{
// We aren't interested in non-readable channels atm
}
// Very important
iterKeys.remove();
}
}
}catch(IOException ex) {
// handle selector exception
}
}
}
}