I’ve recently started playing with the LoadingCache class and I am getting some unexpected behaviour. I now know the solution, but I’m posting the question so others can learn from it.
Basically, I put a non-null value in the cache for a certain key. The key is the value’s position in a file and using the CacheLoader.load( ) method, the value can be read from file. At the moment the LoadingCache is used by a single thread.
When I run my application, the removal notification listener is called with a null value for a key that was previously used to insert a non-null value. I know this because I used assert statements to check that all values that go into the cache are not null (this shouldn’t happen anyway). Also I can see this because I printed the key in the method that gets called by CacheLoader.load( ): CacheLoader.load( ) is the only method that can call the method that prints the key. Furthermore, this method cannot return null values.
I’d be much obliged if somebody could let me know if I’m doing something wrong.
The following is my definition of the loading cache.
private LoadingCache<Long,T> gcache( ) {
@SuppressWarnings( Constants.UNCHECKED )
final CacheLoader<Long,T> loader =
new CacheLoader<Long,T>() {
public T load(Long key) {
// This following method _did_ print the key for which
// the RemovalNotification listener returns a null value.
// See output further on.
return getElement( key );
}
};
@SuppressWarnings( Constants.UNCHECKED )
final RemovalListener<Long,T> listener
= new RemovalListener<Long,T>( ) {
@Override
public void onRemoval( RemovalNotification<Long,T> notification ) {
final T value = notification.getValue( );
////////////////////////////////////////////////////////////////////////////
if (value == null) {
System.out.println( "????: " + notification.getKey( ) );
}
assert( value != null);
////////////////////////////////////////////////////////////////////////////
if (!value.immutable( )) {
put( notification.getKey( ), value );
}
}
};
@SuppressWarnings( Constants.UNCHECKED )
final LoadingCache<Long, T> gcache
= CacheBuilder.newBuilder( )
.softValues( )
.removalListener( listener )
.build( loader );
return gcache;
}
private T getElement( long pos ) {
// Critical section, but at the moment there's only one thread.
enter( );
seek( pos );
final T creation = inflator.inflate( this );
///////////////////////////////////////////////////////////////////////
System.out.println( "getElement: pos = " + pos );
assert( creation != null );
///////////////////////////////////////////////////////////////////////
leave( );
return creation;
}
Some of the output:
[ cut ]
getElement: pos = 362848
[ cut ]
????: 362848
After the question marks I get an uncontrollable amount of output, and the JVM is difficult to stop. I have to hit the key several times before I can terminate the application. (Of course kill would also do the trick, but the keyboard is slightly faster.)
I’m just guessing: In the javadoc to
RemovalNotificationit readsand you’re using
softValues(). When the value gets GC’ed, then the whole entry gets removed from the cache and the key may get collected as well.