【问题标题】:In a list of WeakReferences last item is never garbage collected在 WeakReferences 列表中,最后一项永远不会被垃圾收集
【发布时间】:2017-05-29 12:07:00
【问题描述】:

对于我的服务器应用程序,我使用 Wea​​kReferences 列表来保持计数并处理与服务器的活动会话。我正在运行定期 gc 以清理非活动会话列表,但由于某种原因,始终保留一个参考。根据覆盖的 finalize 方法,这是创建的最后一个会话。

我不知道为什么会这样。我首先认为这可能是由于静态方法或变量,但现在我已经从 ClientHandlerThread 类中删除了这些对象。服务器类没有其他引用,但弱引用列表。目前这对我来说不是一个大问题,但是更好地了解 java 如何选择要进行垃圾收集的对象可以在将来使用。 :) 下面是最重要的代码 sn-ps:

Server.java:

public class Server {

    private List<WeakReference<ClientHandlerThread>> m_connectedClients = 
            Collections.synchronizedList(
                    new ArrayList<WeakReference<ClientHandlerThread>>());

    /** Counter to identify sessions */
    private static AtomicInteger m_NumSession = new AtomicInteger(0);

    Server() {

        SSLServerSocket sslDataTraffic = null;

        // Sockets are initialized here - code removed for clarity

        // Run periodic GC
        Thread stThread = new Thread() {
            public void run() {
                do {
                    try {
                        Thread.sleep(5000);                        
                    }
                    catch (InterruptedException ignore) {}

                    System.runFinalization();
                    System.gc();

                    cleanUpSessionsList();

                } while (true);
            }
        };

        stThread.setPriority(Thread.MIN_PRIORITY);
        stThread.start();

        // Listen to new connections, create handlers and add to list
        while (true) {          
            try {
                SSLSocket sslDataTrafficSocketInstance = 
                        (SSLSocket) sslDataTraffic.accept();

                ClientHandlerThread c = new ClientHandlerThread(
                        sslDataTrafficSocketInstance,
                        m_NumSession.incrementAndGet());

                c.start();
                m_connectedClients.add(new WeakReference<>(c));             

            }  catch (Exception e) {
                e.printStackTrace();
            }

        }
    }



    /** Clean any old references and return the number of active connections
     * @return
     */
     public int cleanUpSessionList() {
        int i = 0;

        synchronized(m_connectedClients) {
            Iterator<WeakReference<ClientHandlerThread>> it = 
                    m_connectedClients.iterator();
            while (it.hasNext()) {
                WeakReference<ClientHandlerThread> sessionRef = it.next();
                if (sessionRef.get() == null)
                    it.remove();
                else
                    i++;
            }
        }

        System.out.println("Active sessions: " + i");

        return i;
    }

}

ClientHandlerThread.java:

public class ClientHandlerThread extends Thread {

    private int m_SessionID;
    private SSLSocket dataSocket;

    public ClientHandlerThread(
            SSLSocket dataSocket,
            int sessionID) {

        this.dataSocket = dataSocket;
        m_SessionID = sessionID;

    }



    public void run() {
        // code removed
    }



    @Override
    protected void finalize() throws Throwable {
        System.out.println("Session " + m_SessionID + " finalized");
        super.finalize();
    }

}

【问题讨论】:

  • 不保证会收集未引用的对象,尤其是当它们覆盖 finalize 时。这种覆盖会产生可怕的后果。这里使用finalize是不恰当的,不应该这样做。
  • 你能澄清你所说的覆盖 finalize 会产生可怕的后果是什么意思吗?在这种情况下,如果调用该方法,我只会打印调试字符串,并且我完全知道根据 java doc 可能永远不会调用它。然而,这个方法“从不”调用的唯一对象似乎是 WeakReference 列表中最新添加的对象 - 直到添加另一个对象。
  • 你是认真地要求我们做你的研究吗?好吧,好吧,但只要你知道几分钟的在线搜索就会产生奇迹。无论如何,具有“非平凡”finalize 的对象至少需要两个 GC 周期来收集,这会增加内存压力。它可能会挂在原本会被收集的引用上,从而增加内存压力。无法保证对finalize 的调用顺序,并且可以跳过对象,将另一轮GC 添加到它们的集合中,并增加内存压力。还有更多。请自行查找。
  • 我同意添加此方法有其缺点,但在这种情况下,无论是否包含该方法,结果都是相同的。困扰我的是所有其他对象如何及时收集,但最新的始终保留。不过,谢谢你的回答。 :)
  • 执行堆快照,通过 eclipse MAT 等内存分析器运行它。看看这些物体上的东西。

标签: java garbage-collection weak-references


【解决方案1】:

这完全是错误的(代码本身还不错,但你做了很多我通常会避免的事情)。

  • 使用ReferenceQueue 而不是finalize
  • 考虑使用 PhantomReference 而不是弱,因为您 AFAICT 不需要访问裁判。
  • 如果您只想对活动会话进行计数,只需对它们进行计数(通过会话跟踪代码围绕处理程序代码)。
  • 您应该使用线程池。
  • 运行定期 GC 会影响性能(尽管它甚至可能有助于提高性能,但您不应依赖它)。

关于问题本身......不知道,但代码中可能有一些东西阻止了最后一个线程的释放。如前所述,执行堆快照,通过内存分析器运行它。

【讨论】:

    【解决方案2】:

    在我发布related question 后发现这个问题作为交叉引用。

    我不知道为什么会发生这种情况,我认为不应该,但我可以告诉你我认为发生了什么,并且我很好奇建议的解决方法是否会改变您所看到的行为。 (如果您仍然有代码;我知道这是一个较老的问题。)

    据我所知,JRE 以某种方式维护了对最后创建的作用域变量的引用。通过将变量设置为 null(或通过创建另一个新的、不相关的作用域变量),这个问题就消失了。

    所以是这样的:

    ClientHandlerThread c = new ClientHandlerThread(
            sslDataTrafficSocketInstance,
            m_NumSession.incrementAndGet());
    
    c.start();
    m_connectedClients.add(new WeakReference<>(c)); 
    c = null;           
    

    再次,我并不是说它应该以这种方式运行,但根据我在类似情况下所做的测试,它是有效的。

    【讨论】:

      猜你喜欢
      • 2017-01-19
      • 1970-01-01
      • 2012-12-19
      • 1970-01-01
      • 2013-05-11
      • 2019-01-14
      • 2023-03-19
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多