0

With my server application I'm using a list of WeakReferences to keep count and handle active sessions to server. I'm running periodic gc to clean the list of inactive sessions, but for some reason one reference always remains. According to overridden finalize method this is the last sessions created.

I'm clueless on why this is happening. I first thought this may have been due to static methods or variables, but for now i have removed such objects from ClientHandlerThread class. There are no other references from the server class but the weak references list. Currently this is not a big issue for me, but to have better understanding on how java selects objects to be garbage collected can be of use in the future. :) Below are most important code snippets:

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();
    }

}
pnkkr
  • 337
  • 3
  • 15
  • 1
    Unreferenced objects are not guaranteed to be collected, particularly if they override `finalize`. That override has dire consequences. The use of `finalize` here is improper and shouldn't be done. – Lew Bloch May 29 '17 at 16:58
  • Can you clarify what you mean by saying overriding finalize has dire consequences? In this case im only printing debug string if the method is called, and I'm fully aware that it may never be called according to java doc. However, it seems that the only object this method is "never" called with is the latest addition in WeakReference list - that is until another object is added. – pnkkr May 29 '17 at 17:05
  • 1
    Are you seriously asking us to do your research? Well, all right then, but just so you know a few minutes of online search will work wonders. Anyway, an object with a "non-trivial" `finalize` needs at least two GC cycles for collection, adding to memory pressure​. It potentially could hang on to references that otherwise would be collected, adding to memory pressure. Order of calls to `finalize` is not guaranteed, and objects could be skipped, adding another round of GC to their collection, and adding to memory pressure. There's more. Please do look it up yourself. – Lew Bloch May 29 '17 at 17:18
  • I agree that adding this method has its downsides, but in this case the result is the same whether the method is included or not. The thing that was bothering me was that how all other objects are collected in time, but the latest one always remains. However, thank you for your answer. :) – pnkkr May 29 '17 at 18:22
  • 1
    perform a heap snapshot, run it through a memory analyzer such as eclipse MAT. see what holds onto those objects. – the8472 May 30 '17 at 10:36

2 Answers2

0

That's about all wrong (the code itself isn't bad, but you're doing many things I'd usually avoid).

  • Use a ReferenceQueue instead of finalize.
  • Consider using a PhantomReference instead of weak as you AFAICT don't need to access the referee.
  • If all you want is to count active sessions, the simply count them (surround the handler code by session tracking code).
  • You should use a thread pool.
  • Running periodic GC can impact performance (though it may even help the performance, you should not rely on it).

Concerning the question itself... no idea, but there may be something in the code blocking the last thread from freeing. as already suggested, perform a heap snapshot, run it through a memory analyzer.

maaartinus
  • 44,714
  • 32
  • 161
  • 320
0

Found this question as a cross-reference after I posted a related question.

I don't have an answer as to why it's happening, and I think it shouldn't, but I can tell you what I think is happening, and I'm curious if a suggested workaround changes the behavior you're seeing. (If you still have the code lying around; I know it's an older question.)

As far as I can tell, somehow the JRE maintains a reference to the last scoped variable that gets created. By setting the variable to null (or by creating another, new, unrelated scoped variable) this problem goes away.

So something like this:

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

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

Again, I'm not saying it should behave this way, but from the testing I've done in my similar situation, it works.

jwismar
  • 12,164
  • 3
  • 32
  • 44