United StatesChange Country, Oracle Worldwide Web Sites Communities I am a... I want to...
JDK-4486732 : RMI custom client socket factories not getting garbage collected

Details
Type:
Bug
Submit Date:
2001-08-01
Status:
Resolved
Updated Date:
2005-10-07
Project Name:
JDK
Resolved Date:
2005-10-07
Component:
core-libs
OS:
windows_nt,linux_redhat_3.0
Sub-Component:
java.rmi
CPU:
x86,sparc
Priority:
P3
Resolution:
Fixed
Affected Versions:
1.3.1,5.0
Fixed Versions:

Related Reports
Backport:
Relates:

Sub Tasks

Description
Name: boT120536			Date: 07/31/2001


java version "1.2.2" (and 1.3.1, per user)
Classic VM (build JDK-1.2.2_006, native threads, symcjit)



When an RMI client application gets a reference to a server object that uses a
custom client socket factory, the factory gets serialized and instantiated on
the client JVM.  When references to the remote object are released, the object
implementing RMIClientSocketFactory is not released and garbage collected.

Working with some tools I was able to determine that there are
sun.rmi.transport.tcp.TCPEndpoint objects holding references to the client
socket factory.  Furthermore, there seems to be circular references between
these objects and TCPTransport and TCPChannel objects (all in the
sun.rmi.transport.tcp package).

This problem does not seem to happen when using the standard rmi socket
factories, but apparently (per the RMI documentation) the endpoint objects and
data are handled differently when using custom factories.

This problem is also present in JDK 1.3.1.

STEPS TO REPRODUCE:
- Create an RMI server object (factory) that will create instances of another
server object on demand (a session object).  Have the session object use custom
client and server socket factories.

- Create a client that looks up the factory object and then loops to request
session objects, make a simple call to them and then releases them.  Using a
memory tool (I'm using OptimizeIT), you can see that instances of the socket
factories are created on the client that are then not released when the session
objects are released.  Using a debugger that will show threads, it seems like
there are two threads that are created for every remote reference that have
something to do with the client socket factory.  One of the threads seems to be
finishing and getting collected when the reference is lost.  The other thread,
however, seems to stay around indefinitely.

SOURCE CODE:

There are two packages, client and server:

  The client package contains a single class that finds the registry, gets a
reference to the factory object, and then calls the factory in a loop to create
a number of session objects.  After the loop is done, it calls the garbage
collection, sleeps for some time (30s), calls garbage collection again and
sleeps for some more time (60s).  I was trying to get force garbage
collection.  The second sleep call is to have time to look at threads and
objects using a debugger or an object inspector.  These sleep times can of
course be changed to higher values.

The server factory has the factory object and interface, the session object and
interface and the socket factories, client and server.  I'm not including the
code for the stubs as this can be generated using rmic.

Following is the source code for the client:

package client;

import java.rmi.*;
import java.rmi.server.*;
import java.rmi.registry.*;

import server.*;

public class TestClient
{

public static void main(String[] args)
{
	try
	{
		Registry registry = LocateRegistry.getRegistry ();
		IFactory factory = (IFactory)registry.lookup ("factory");

		for (int count = 0; count < 50; ++count)
		{
			ISession session = factory.getSession();
			session.dummyCall();
		}

		System.gc();
		System.out.println ("client sleeping...");
		Thread.currentThread().sleep (30000);
		System.gc ();
		System.out.println ("client sleeping...");
		Thread.currentThread().sleep (60000);
	}
	catch (Throwable t)
	{
		t.printStackTrace ();
	}
}
}


Server source code:

package server;

import java.rmi.server.*;
import java.net.*;
import java.io.*;

public class ClientSocketFactory implements RMIClientSocketFactory, Serializable
{
public Socket createSocket(String host, int port) throws IOException
{
	return new Socket(host, port);
}
}



package server;

import java.rmi.*;
import java.rmi.server.*;
import java.rmi.registry.*;

public class Factory extends UnicastRemoteObject implements IFactory
{
	RMIClientSocketFactory m_ClientSocketFactory;
	RMIServerSocketFactory m_ServerSocketFactory;
protected Factory() throws RemoteException
{
	super();

	m_ClientSocketFactory = new ClientSocketFactory ();
	m_ServerSocketFactory = new ServerSocketFactory ();
}
public ISession getSession() throws RemoteException
{
	return new Session (1100, m_ClientSocketFactory, m_ServerSocketFactory);
}
public static void main(String[] args)
{
	try
	{
		// Create a registry
		Registry registry = LocateRegistry.createRegistry(1099);
		
		// Create and bind the factory
		Factory factory = new Factory ();
		registry.rebind ("factory", factory);

		System.out.println ("Server is ready.");
	}
	catch (Throwable t)
	{
		t.printStackTrace ();
	}
}
}


package server;

import java.rmi.*;
import java.rmi.server.*;
import java.rmi.registry.*;

public interface IFactory extends Remote
{
public ISession getSession() throws RemoteException;
}


package server;

import java.rmi.*;
import java.rmi.server.*;
import java.rmi.registry.*;

public interface ISession extends Remote
{
public void dummyCall() throws RemoteException;
}


package server;

import java.io.*;
import java.rmi.server.*;
import java.net.*;

public class ServerSocketFactory implements RMIServerSocketFactory, Serializable
{
public ServerSocket createServerSocket(int port) throws IOException
{
	return new ServerSocket(port);
}
}


package server;

import java.rmi.*;
import java.rmi.server.*;
import java.rmi.registry.*;

public class Session extends UnicastRemoteObject implements ISession
{
public Session(int port, java.rmi.server.RMIClientSocketFactory csf,
java.rmi.server.RMIServerSocketFactory ssf) throws java.rmi.RemoteException {
	super(port, csf, ssf);
}
public void dummyCall() throws RemoteException
{
}
}
(Review ID: 128716) 
======================================================================

                                    

Comments
EVALUATION

I am considering this bug to be specific to the case of an RMIClientSocketFactory instance getting pinned forever as a result of a remote invocation made on a stub containing that factory instance (either a registry stub returned from LocateRegistry.getRegistry or a normally deserialized/unmarshalled stub).  This consideration covers the problem described in this bug report, and it refers to the problem of an RMI "client" application accumulating (never freeing) the client socket factories for remote objects that it happens to use over the course of the lifetime of its VM.

It is possible to fix this bug with a reasonably low-risk, surgical modification to the existing implementation; therefore, it should be done for Mustang (with perhaps additional code cleanup being done in a future release).

There remains the separate issue that an RMIClientSocketFactory or RMIServerSocketFactory instance used to export a remote object (with UnicastRemoteObject, Activatable, or LocateRegistry.createRegistry) also gets pinned forever in the remote object's VM.  Fixing this problem would seem to require riskier implementation changes.  It will be filed as a separate bug.
                                     
2005-10-03
EVALUATION

I have reproduced the bug as described, in that I observed the ClientSocketFactory instances not getting garbage collected.

[Note that the client-side DGC "RenewClean" thread per socket factory vanished fairly quickly after garbage collection, and the client-side "ConnectionExpiration" thread per socket factory also vanished after the timeout (~15 seconds)-- perhaps the submitter had not waited long enough when observing those threads still alive?  Also, it's not clear whether the submitter intended to omit hashCode/equals methods from the socket factory classes, to simulate many non-equivalent socket factory instances, or if they were just forgotten, but either way, this bug should be fixed, because many non-equivalent socket factory instances are conceivable for some applications.]

The garbage collection problem stems from the fact that the cache of outbound "channels" (used by clients to make connections) is always contained in the "local transport" for the given custom client socket factory, and these local transports are never released.  Normally, there are not many such local transport objects (only one if no custom client socket factories are being used, because the destination port is not considered), but if there are a large number of different (non-equivalent) client socket factories instances that calls are made through, then the need for a separate one per client socket factory will cause there to be many local transports (perhaps all just used for client-side activity...), and they will each forever pin their associated client socket factory.

There is no particularly compelling reason why the cache of outbound connections needs to be stored in the "local transport" object-- it's really pretty irrelevant to the rest of the transport object's behavior (which is mostly server-side)-- so instead, the outbound channel caches might more reasonably be stored in a global table keyed on the outbound endpoint.  Of course, it would still have to be reaped when no more equivalent endpoint instances exist in the VM (perhaps just using the way that DGCClient causes endpoints to be removed from the channel cache), but then there would be no "local transport" sitting around forever holding on to the client socket factory instance, thus addressing the bug on the client side.

Note that there would still be a problem of pinned client/server socket factory instances on the server-side, if in the same VM there are exported and unexported remote objects with many non-equivalent client/server socket factory instances, because "local transport" would still be created (and never released) for all of them-- this should probably be fixed as well.

peter.c.jones@east 2001-08-01

Regarding the JDC comment of 2003-07-28: the described behavior-- leaking of "RenewClean" threads-- is likely caused by 6199638 (as opposed to this bug).
###@###.### 2004-11-29 23:22:35 GMT
                                     
2004-11-29
WORK AROUND



Name: boT120536			Date: 07/31/2001


I have not been able to find a workaround yet.
======================================================================

Note that when this bug is encountered, it is important to eliminate the possibility that the socket factory classes in question have not properly overridden Object.hashCode/equals to implement functional equivalence (see 4492317).  If a socket factory class has not overridden hashCode/equals appropriately, that could cause massive exacerbation of this bug, as each time an equivalent socket factory instance is unmarshalled, it could be treated as new and different by the RMI runtime.  See the Description of 4492371 for more details:

http://developer.java.sun.com/developer/bugParade/bugs/4492317.html

###@###.### 2002-03-14
                                     
2002-03-14



Hardware and Software, Engineered to Work Together