Reputation: 892
I am trying to build a server but the use of a thread pool has introduced a memory leak that causes my heap to over flow.
My server creates two primary threads from the main thread, the Acceptor and the Consumer. I am new to JProfiler but it seemed to very clearly point me to the below code (the Acceptor) as the culprit.
The Acceptor thread accepts new connections from clients. Upon an accepted call a new thread from the thread pool is used to handle the connection.
public class AcceptorThread implements Runnable{
LinkedBlockingQueue<LinkedList<Log_Message>> queue = null;
public AcceptorThread(LinkedBlockingQueue<LinkedList<Log_Message>> queue){
this.queue = queue;
}
@Override
public void run() {
ExecutorService acceptors = Executors.newCachedThreadPool();
ServerSocket socket = null;
try {
socket = new ServerSocket(44431);
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
while(!init_LogServer.isStopped()){
Socket connectionSocket = null;
try {
connectionSocket = socket.accept();
} catch (IOException e) {
e.printStackTrace();
}
init_LogServer.addList();
System.out.println("Accepted New List: " + init_LogServer.getListCount());
Runnable worker = new List_Acceptor(connectionSocket, queue);
acceptors.execute(worker);
}
}
}
The new thread from the thread pool receives a linked list of objects, this list is added to the queue and the thread is complete. My understanding was that upon completion of the below code the thread would terminate, the below object would be collected by the GC and the thread would re-enter the thread pool for reuse. I can't see any obvious little memory leaks so I'm guessing what happens is that the below class is never collected by the GC causing the heap to build, is that correct? How can I destroy the below object once the received list is added to the queue?
public class List_Acceptor implements Runnable{
Socket socket = null;
private LinkedList<Log_Message> lmList = null;
private LinkedBlockingQueue<LinkedList<Log_Message>> queue = null;
private String dumpMessage = null;
public List_Acceptor(Socket socket, LinkedBlockingQueue<LinkedList<Log_Message>> queue){
this.queue = queue;
this.socket = socket;
}
@Override
public void run() {
ObjectInputStream input = null;
try {
input = new ObjectInputStream(socket.getInputStream());
} catch (IOException e3) {
// TODO Auto-generated catch block
e3.printStackTrace();
}
Object received = null;
try {
received = input.readObject();
} catch (ClassNotFoundException | IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
try {
lmList = (LinkedList<Log_Message>) received;
} catch (Exception e1) {
//e1.printStackTrace();
System.out.println("Received DumpLog");
dumpMessage = (String) received;
System.out.println("Dump: "+dumpMessage);
init_LogServer.stop();
e1.printStackTrace();
}
//close socket
try {
socket.close();
input.close();
} catch (IOException e2) {e2.printStackTrace();}
try {
queue.put(lmList);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
EDIT: I have learnt that apparently when an object/class is instantiated via a thread as I do with the thread pool the GC is restricted from cleaning that object. This seems a likely cause for memory leakage and is possibly my problem. With that in mind tomorrow I will redesign so that the AcceptorTread does the necessary steps to put the received list into the queue, it should be slower but will prevent the heap problem. Does that make sense?
Upvotes: 1
Views: 4003
Reputation: 8716
If your threads keep references to objects around after they finish a request only to overwrite them when a new request comes in, then the original objects are "pinned" into memory until that thread again becomes active. These pinned objects get copied numerous times and take up ram only to be released later on.
Worker threads that loop should always clear out all their object references before waiting for another object on which to work.
Upvotes: 0
Reputation: 47965
Regarding your last edit: The current thread will not keep all your created List_Acceptor
objects alive (BTW, the Java naming convention is ListAcceptor
).
The allocation hot spots view in JProfiler just tells you were objects that are still on the heap are allocated. It does not mean that those objects cannot be GCed.
For an analysis of what objects are actually strongly referenced, go to the "Heap walker" in JProfiler. Then select all List_Acceptor
objects and go to the "Incoming references" view. With a single object, click on "Show path to GC root". Then you will see a chain of references that prevents the object from being GCed.
With the "Cumulated incoming reference" view you can check whether this is the case for all List_Acceptor
objects.
Upvotes: 1