HHH-13579 Optimise ResourceRegistryStandardImpl to avoid heavy allocation of iterators

This commit is contained in:
Sanne Grinovero 2019-08-14 13:36:37 +01:00
parent 927f4c2ffc
commit 0b64cef2b3
1 changed files with 58 additions and 45 deletions

View File

@ -13,13 +13,7 @@ import java.sql.ResultSet;
import java.sql.SQLException; import java.sql.SQLException;
import java.sql.Statement; import java.sql.Statement;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.hibernate.HibernateException; import org.hibernate.HibernateException;
import org.hibernate.JDBCException; import org.hibernate.JDBCException;
@ -29,19 +23,40 @@ import org.hibernate.resource.jdbc.ResourceRegistry;
import org.hibernate.resource.jdbc.spi.JdbcObserver; import org.hibernate.resource.jdbc.spi.JdbcObserver;
/** /**
* Helps to track statements and resultsets which need being closed.
* This class is not threadsafe.
*
* Note regarding performance: we had evidence that allocating Iterators
* to implement the cleanup on each element recursively was the dominant
* resource cost, so we decided using "forEach" and lambdas in this case.
* However the forEach/lambda combination is able to dodge allocating
* Iterators on HashMap and ArrayList, but not on HashSet (at least on JDK8 and 11).
* Therefore some types which should ideally be modelled as a Set have
* been implemented using HashMap.
*
* @author Steve Ebersole * @author Steve Ebersole
* @author Sanne Grinovero
*/ */
public class ResourceRegistryStandardImpl implements ResourceRegistry { public final class ResourceRegistryStandardImpl implements ResourceRegistry {
private static final CoreMessageLogger log = CoreLogging.messageLogger( ResourceRegistryStandardImpl.class ); private static final CoreMessageLogger log = CoreLogging.messageLogger( ResourceRegistryStandardImpl.class );
// Dummy value to associate with an Object in the backing Map when we use it as a set:
private static final Object PRESENT = new Object();
//Used instead of Collections.EMPTY_SET to avoid polymorhic calls on xref;
//Also, uses an HashMap as it were an HashSet, as technically we just need the Set semantics
//but in this case the overhead of HashSet is not negligible.
private static final HashMap<ResultSet,Object> EMPTY = new HashMap<ResultSet,Object>( 1, 0.2f );
private final JdbcObserver jdbcObserver; private final JdbcObserver jdbcObserver;
private final Map<Statement, Set<ResultSet>> xref = new HashMap<Statement, Set<ResultSet>>(); private final HashMap<Statement, HashMap<ResultSet,Object>> xref = new HashMap<>();
private final Set<ResultSet> unassociatedResultSets = new HashSet<ResultSet>(); private final HashMap<ResultSet,Object> unassociatedResultSets = new HashMap<ResultSet,Object>();
private List<Blob> blobs; private ArrayList<Blob> blobs;
private List<Clob> clobs; private ArrayList<Clob> clobs;
private List<NClob> nclobs; private ArrayList<NClob> nclobs;
private Statement lastQuery; private Statement lastQuery;
@ -67,7 +82,7 @@ public class ResourceRegistryStandardImpl implements ResourceRegistry {
public void register(Statement statement, boolean cancelable) { public void register(Statement statement, boolean cancelable) {
log.tracef( "Registering statement [%s]", statement ); log.tracef( "Registering statement [%s]", statement );
Set<ResultSet> previousValue = xref.putIfAbsent( statement, Collections.EMPTY_SET ); HashMap<ResultSet,Object> previousValue = xref.putIfAbsent( statement, EMPTY );
if ( previousValue != null ) { if ( previousValue != null ) {
throw new HibernateException( "JDBC Statement already registered" ); throw new HibernateException( "JDBC Statement already registered" );
} }
@ -81,7 +96,7 @@ public class ResourceRegistryStandardImpl implements ResourceRegistry {
public void release(Statement statement) { public void release(Statement statement) {
log.tracev( "Releasing statement [{0}]", statement ); log.tracev( "Releasing statement [{0}]", statement );
final Set<ResultSet> resultSets = xref.remove( statement ); final HashMap<ResultSet,Object> resultSets = xref.remove( statement );
if ( resultSets != null ) { if ( resultSets != null ) {
closeAll( resultSets ); closeAll( resultSets );
} }
@ -111,7 +126,7 @@ public class ResourceRegistryStandardImpl implements ResourceRegistry {
} }
} }
if ( statement != null ) { if ( statement != null ) {
final Set<ResultSet> resultSets = xref.get( statement ); final HashMap<ResultSet,Object> resultSets = xref.get( statement );
if ( resultSets == null ) { if ( resultSets == null ) {
log.unregisteredStatement(); log.unregisteredStatement();
} }
@ -123,19 +138,16 @@ public class ResourceRegistryStandardImpl implements ResourceRegistry {
} }
} }
else { else {
final boolean removed = unassociatedResultSets.remove( resultSet ); final Object removed = unassociatedResultSets.remove( resultSet );
if ( !removed ) { if ( removed == null ) {
log.unregisteredResultSetWithoutStatement(); log.unregisteredResultSetWithoutStatement();
} }
} }
close( resultSet ); close( resultSet );
} }
protected void closeAll(Set<ResultSet> resultSets) { protected void closeAll(final HashMap<ResultSet,Object> resultSets) {
for ( ResultSet resultSet : resultSets ) { resultSets.forEach( (resultSet, o) -> close( resultSet ) );
close( resultSet );
}
resultSets.clear(); resultSets.clear();
} }
@ -202,7 +214,7 @@ public class ResourceRegistryStandardImpl implements ResourceRegistry {
} }
} }
if ( statement != null ) { if ( statement != null ) {
Set<ResultSet> resultSets = xref.get( statement ); HashMap<ResultSet,Object> resultSets = xref.get( statement );
// Keep this at DEBUG level, rather than warn. Numerous connection pool implementations can return a // Keep this at DEBUG level, rather than warn. Numerous connection pool implementations can return a
// proxy/wrapper around the JDBC Statement, causing excessive logging here. See HHH-8210. // proxy/wrapper around the JDBC Statement, causing excessive logging here. See HHH-8210.
@ -210,14 +222,14 @@ public class ResourceRegistryStandardImpl implements ResourceRegistry {
log.debug( "ResultSet statement was not registered (on register)" ); log.debug( "ResultSet statement was not registered (on register)" );
} }
if ( resultSets == null || resultSets == Collections.EMPTY_SET ) { if ( resultSets == null || resultSets == EMPTY ) {
resultSets = new HashSet<ResultSet>(); resultSets = new HashMap<ResultSet,Object>();
xref.put( statement, resultSets ); xref.put( statement, resultSets );
} }
resultSets.add( resultSet ); resultSets.put( resultSet, PRESENT );
} }
else { else {
unassociatedResultSets.add( resultSet ); unassociatedResultSets.put( resultSet, PRESENT );
} }
} }
@ -303,50 +315,51 @@ public class ResourceRegistryStandardImpl implements ResourceRegistry {
jdbcObserver.jdbcReleaseRegistryResourcesStart(); jdbcObserver.jdbcReleaseRegistryResourcesStart();
} }
for ( Map.Entry<Statement, Set<ResultSet>> entry : xref.entrySet() ) { xref.forEach(
if ( entry.getValue() != null ) { (Statement s, HashMap<ResultSet,Object> r) -> {
closeAll( entry.getValue() ); closeAll( r );
} close( s );
close( entry.getKey() );
} }
);
xref.clear(); xref.clear();
closeAll( unassociatedResultSets ); closeAll( unassociatedResultSets );
if ( blobs != null ) { if ( blobs != null ) {
for ( Blob blob : blobs ) { blobs.forEach( blob -> {
try { try {
blob.free(); blob.free();
} }
catch (SQLException e) { catch (SQLException e) {
log.debugf( "Unable to free JDBC Blob reference [%s]", e.getMessage() ); log.debugf( "Unable to free JDBC Blob reference [%s]", e.getMessage() );
} }
} } );
blobs.clear(); //for these, it seems better to null the map rather than clear it:
blobs = null;
} }
if ( clobs != null ) { if ( clobs != null ) {
for ( Clob clob : clobs ) { clobs.forEach( clob -> {
try { try {
clob.free(); clob.free();
} }
catch (SQLException e) { catch (SQLException e) {
log.debugf( "Unable to free JDBC Clob reference [%s]", e.getMessage() ); log.debugf( "Unable to free JDBC Clob reference [%s]", e.getMessage() );
} }
} } );
clobs.clear(); clobs = null;
} }
if ( nclobs != null ) { if ( nclobs != null ) {
for ( NClob nclob : nclobs ) { nclobs.forEach( nclob -> {
try { try {
nclob.free(); nclob.free();
} }
catch (SQLException e) { catch (SQLException e) {
log.debugf( "Unable to free JDBC NClob reference [%s]", e.getMessage() ); log.debugf( "Unable to free JDBC NClob reference [%s]", e.getMessage() );
} }
} } );
nclobs.clear(); nclobs = null;
} }
if ( jdbcObserver != null ) { if ( jdbcObserver != null ) {
@ -354,11 +367,11 @@ public class ResourceRegistryStandardImpl implements ResourceRegistry {
} }
} }
private boolean hasRegistered(Map resource) { private boolean hasRegistered(final HashMap resource) {
return resource != null && !resource.isEmpty(); return resource != null && !resource.isEmpty();
} }
private boolean hasRegistered(Collection resource) { private boolean hasRegistered(final ArrayList resource) {
return resource != null && !resource.isEmpty(); return resource != null && !resource.isEmpty();
} }
} }