Inside our system we present session of client as class Session.
Historically hashcode of this class is mutable - it is 0 on creation and change to user id at some point of time.
There are existed two session managers in the system.
- Client Sessions Manager - hold active client session is the client. Inside of this manager is simple ConcurrentHashMap< Long, Session > where key is user id.
- Memory Sessions Manager - hold sessions which are not yet collected by GC. Inside there are WeakHashMap < Session, Long > (value here is user id)
When client connects and logged in next flow happens:
- Client session is created.
- Session put into Memory Sessions Manager (hashcode at this point is 0 so they are put into single bucket)
- Client is logged in and session put into Client Sessions Manager with correct hash code.
- Client session is closed and removed from Client Sessions Manager.
As result after collection of all strong references on Session object it must be removed from WeakHashMap. (An entry in a WeakHashMap will automatically be removed when its key is no longer in ordinary use. More precisely, the presence of a mapping for a given key will not prevent the key from being discarded by the garbage collector, that is, made finalizable, finalized, and then reclaimed.) But for some reason they are stayed there. Code of it below:
public class MemorySessionManager implements Runnable {
private static final Logger logger = LoggerFactory.getLogger("checker");
private Map<Session, Long> sessions;
public MemorySessionManager() {
this.sessions = new WeakHashMap<>();
}
public synchronized void addSession(Session sess) {
sessions.put(sess, sess.getId());
}
public void run() {
Set<Session> sessionsToCheck = new HashSet<>();
synchronized (this) {
sessionsToCheck.addAll(sessions.keySet());
}
for (Session sess : sessionsToCheck) {
logger.warn("MemorySessionManager: Is still here: " + sess);
}
}
}
Simplified program flow and session class (without useless info).
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
public class ClientSessionManager {
private Map<Long, Session> sessions;
public ClientSessionManager() {
this.sessions = new ConcurrentHashMap<>();
}
public void addSession(Session session) {
sessions.put(session.getUserId(), session);
}
public Session removeSession(long code) {
return sessions.remove(code);
}
}
public class Session {
private long userId;
public void setUserId(long userId) {
this.userId = userId;
}
public long getUserId() {
return userId;
}
@Override
public boolean equals(Object o) {
if (this == o)
return true;
if (o == null || getClass() != o.getClass())
return false;
Session session = (Session) o;
return userId == session.userId;
}
@Override
public int hashCode() {
return (int) (userId ^ (userId >>> 32));
}
}
public class Process {
private static final ClientSessionManager CLIENT_SESSION_MANAGER = new ClientSessionManager();
private static final MemorySessionManager MEMORY_SESSION_MANAGER = new MemorySessionManager();
/**
* Login user, create its session and register session into appropriate managers.
*
* @param userId id of user
*/
public void login(long userId) {
Session session = new Session();
MEMORY_SESSION_MANAGER.addSession(session);
session.setUserId(userId);
CLIENT_SESSION_MANAGER.addSession(session);
}
/**
* Close session of user, remove it from session manager
*
* @param userId id of user
*/
public void close(long userId) {
CLIENT_SESSION_MANAGER.removeSession(userId);
}
}
After lots of GC cycles in remains in memory. Here is logs on GC (G1, mixed)
[GC pause (G1 Evacuation Pause) (mixed)
Desired survivor size 6815744 bytes, new threshold 4 (max 15)
- age 1: 236400 bytes, 236400 total
- age 2: 350240 bytes, 586640 total
- age 3: 3329024 bytes, 3915664 total
- age 4: 2926992 bytes, 6842656 total
, 0.0559520 secs]
[Parallel Time: 51.9 ms, GC Workers: 2]
[GC Worker Start (ms): Min: 73278041.7, Avg: 73278041.8, Max: 73278042.0, Diff: 0.2]
[Ext Root Scanning (ms): Min: 3.2, Avg: 3.4, Max: 3.7, Diff: 0.5, Sum: 6.9]
[Update RS (ms): Min: 15.0, Avg: 15.0, Max: 15.0, Diff: 0.0, Sum: 30.1]
[Processed Buffers: Min: 59, Avg: 65.0, Max: 71, Diff: 12, Sum: 130]
[Scan RS (ms): Min: 13.6, Avg: 14.3, Max: 15.1, Diff: 1.5, Sum: 28.7]
[Code Root Scanning (ms): Min: 0.4, Avg: 1.2, Max: 2.0, Diff: 1.5, Sum: 2.4]
[Object Copy (ms): Min: 17.5, Avg: 17.7, Max: 17.8, Diff: 0.3, Sum: 35.4]
[Termination (ms): Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.0]
[Termination Attempts: Min: 1, Avg: 1.0, Max: 1, Diff: 0, Sum: 2]
[GC Worker Other (ms): Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.1]
[GC Worker Total (ms): Min: 51.6, Avg: 51.7, Max: 51.8, Diff: 0.2, Sum: 103.4]
[GC Worker End (ms): Min: 73278093.6, Avg: 73278093.6, Max: 73278093.6, Diff: 0.0]
[Code Root Fixup: 0.7 ms]
[Code Root Purge: 0.0 ms]
[Clear CT: 0.4 ms]
[Other: 2.9 ms]
[Choose CSet: 0.6 ms]
[Ref Proc: 0.3 ms]
[Ref Enq: 0.0 ms]
[Redirty Cards: 0.3 ms]
[Humongous Register: 0.1 ms]
[Humongous Reclaim: 0.0 ms]
[Free CSet: 0.9 ms]
[Eden: 95.0M(95.0M)->0.0B(1223.0M) Survivors: 7168.0K->5120.0K Heap: 512.0M(2048.0M)->363.5M(2048.0M)]
[Times: user=0.11 sys=0.00, real=0.06 secs]
2016-09-28T10:40:00.815+0000: 73288.384: [GC pause (G1 Evacuation Pause) (young)
Desired survivor size 80740352 bytes, new threshold 15 (max 15)
- age 1: 1096960 bytes, 1096960 total
- age 2: 220208 bytes, 1317168 total
- age 3: 349352 bytes, 1666520 total
- age 4: 3325200 bytes, 4991720 tota
At pic below you can see path to root of problem session and you can see that only weak references chain exists.
Please help. Or at least give some suggestions.