Performance work

1. types now keep track of their subtypes, enabling smarters checks
   after reloading: "has anything in my hierarchy been reloaded?"
   instead of "has anything at all been reloaded?"

2. Building on that, caches related to info built during reflective
   calls like 'getDeclaredMethods()' and 'getMethods()' is cleared
   from types (and subtypes) during reload. The caches will
   subsequently be rebuilt/reused on the next request for that
   info.

3. New guards in ivicheck/etc: 'have things in my
   hierarchy been reloaded?'
This commit is contained in:
Andy Clement
2014-05-20 11:29:23 -07:00
parent 65a333238c
commit 8c49ba3822
13 changed files with 259 additions and 14 deletions

View File

@@ -95,11 +95,17 @@ public class ReloadableType {
private Class<?> superclazz;
private ReloadableType superRtype;
private ReloadableType[] interfaceRtypes;
List<Reference<ReloadableType>> associatedSubtypes = null;
/** Caches Method objects for this reloadable type. This cache should be invalidated (set to null) when a type is reloaded! */
private JavaMethodCache javaMethodCache;
private final static int IS_RESOLVED = 0x0001;
// Indicates that this type or one in its hierarchy (super/sub) has been reloaded
private final static int IMPACTED_BY_RELOAD = 0x0002;
private int bits;
@@ -402,16 +408,64 @@ public class ReloadableType {
} else {
liveVersion.staticInitializedNeedsRerunningOnDefine = false;
}
// For performance:
// - tag the relevant types that may have been affected by this being reloaded, i.e. this type and any reloadable types in the same hierachy
tagAsAffectedByReload();
tagSupertypesAsAffectedByReload();
tagSubtypesAsAffectedByReload();
typeRegistry.fireReloadEvent(this, versionsuffix);
reloadProxiesIfNecessary(versionsuffix);
}
// dump(newbytedata);
// dump(newbytedata);
return reload;
}
private void tagSupertypesAsAffectedByReload() {
ReloadableType superRtype = getSuperRtype();
if (superRtype!=null) {
superRtype.tagAsAffectedByReload();
// need to recurse up with the tagging
superRtype.tagSupertypesAsAffectedByReload();
}
// need to recurse through super interfaces too
ReloadableType[] superinterfaceRtypes = getInterfacesRtypes();
if (superinterfaceRtypes!=null) {
for (ReloadableType superinterfaceRtype: superinterfaceRtypes) {
superinterfaceRtype.tagAsAffectedByReload();
superinterfaceRtype.tagSupertypesAsAffectedByReload();
}
}
}
// TODO who is clearing up dead entries?
private void tagSubtypesAsAffectedByReload() {
if (associatedSubtypes !=null) {
for (Reference<ReloadableType> ref: associatedSubtypes) {
ReloadableType rsubtype = ref.get();
if (rsubtype != null) {
rsubtype.tagAsAffectedByReload();
rsubtype.tagSubtypesAsAffectedByReload();
}
}
}
}
private void tagAsAffectedByReload() {
bits |= IMPACTED_BY_RELOAD;
invokersCache_getMethods = null;
invokersCache_getDeclaredMethods = null;
}
public boolean isAffectedByReload() {
return (bits&IMPACTED_BY_RELOAD)!=0;
}
// TODO cache these field objects to avoid digging for them every time?
/**
* When an enum type is reloaded, two caches need to be cleared out from the Class object for the enum type.
@@ -984,6 +1038,10 @@ public class ReloadableType {
public String getSlashedSupertypeName() {
return getTypeDescriptor().getSupertypeName();
}
public String[] getSlashedSuperinterfacesName() {
return getTypeDescriptor().getSuperinterfacesName();
}
@UsedByGeneratedCode
public __DynamicallyDispatchable getDispatcher() {
@@ -1467,21 +1525,101 @@ public class ReloadableType {
this.superclazz = superclazz;
}
/**
* Return the ReloadableType representing the superclass of this type. If the supertype
* is not reloadable, this method will return null. The ReloadableType that is returned
* may not be within the same type registry, if the supertype was loaded by a different
* classloader.
*
* @return the ReloadableType for the supertype or null if it is not reloadable
*/
public ReloadableType getSuperRtype() {
if (superRtype != null) {
return superRtype;
}
if (superclazz == null) {
return null;
} else {
// Not filled in yet? Why is this code different to the interface case?
String name = this.getSlashedSupertypeName();
if (name == null) {
return null;
}
else {
ReloadableType rtype = typeRegistry.getReloadableSuperType(name);
superRtype = rtype;
return superRtype;
}
}
else {
ClassLoader superClassLoader = superclazz.getClassLoader();
TypeRegistry superTypeRegistry = TypeRegistry.getTypeRegistryFor(superClassLoader);
superRtype = superTypeRegistry.getReloadableType(superclazz);
return superRtype;
}
}
public ReloadableType[] getInterfacesRtypes() {
if (interfaceRtypes != null) {
return interfaceRtypes;
}
if (this.getSlashedSuperinterfacesName() == null) {
return null;
} else {
List<ReloadableType> reloadableInterfaces = new ArrayList<ReloadableType>();
String[] names = this.getSlashedSuperinterfacesName();
for (String name: names) {
ReloadableType interfaceRtype = typeRegistry.getReloadableSuperType(name);
if (interfaceRtype != null) { // If null then that interface is not reloadable
reloadableInterfaces.add(interfaceRtype);
}
}
interfaceRtypes = reloadableInterfaces.toArray(new ReloadableType[reloadableInterfaces.size()]);
return interfaceRtypes;
}
}
public boolean hasStaticInitializer() {
return this.typedescriptor.hasClinit();
}
/**
* @param child the new reloadable subtype to record
*/
public void recordSubtype(ReloadableType child) {
if (associatedSubtypes == null) {
associatedSubtypes = new ArrayList<Reference<ReloadableType>>();
}
associatedSubtypes.add(new WeakReference<ReloadableType>(child));
if (this.isAffectedByReload()) {
child.tagAsAffectedByReload();
child.tagSubtypesAsAffectedByReload();
}
}
public List<Reference<ReloadableType>> getAssociatedSubtypes() {
return associatedSubtypes;
}
/**
* For this specified reloadable type, records the type with its parent types
* (super class and super interfaces). With this information the system can run faster
* when reloading has occurred.
*/
public void createTypeAssociations() {
// Connect the child to the parent rtype and interface rtypes
ClassLoader classLoader = getClazz().getClassLoader();
if (classLoader == null) {
return;
}
ReloadableType srtype = getSuperRtype();
if (srtype!=null) {
srtype.recordSubtype(this);
}
ReloadableType[] irtypes = getInterfacesRtypes();
if (irtypes!=null) {
for (ReloadableType irtype: irtypes) {
irtype.recordSubtype(this);
}
}
}
}

View File

@@ -1176,6 +1176,11 @@ public class TypeRegistry {
if (reloadableType == null) {
reloadableType = searchForReloadableType(typeId, typeRegistry);
}
// Check 2: Info computed earlier
if (reloadableType!=null && !reloadableType.isAffectedByReload()) {
return null;
}
if (reloadableType != null && reloadableType.hasBeenReloaded()) {
MethodMember method = reloadableType.getLiveVersion().incrementalTypeDescriptor
@@ -1318,6 +1323,12 @@ public class TypeRegistry {
@UsedByGeneratedCode
public static __DynamicallyDispatchable ispcheck(int ids, String nameAndDescriptor) {
// TOD why no check about whether anything has been reloaded???
if (nothingReloaded) {
return null;
}
if (GlobalConfiguration.isRuntimeLogging && log.isLoggable(Level.FINER)) {
log.entering("TypeRegistry", "spcheck", new Object[] { ids, nameAndDescriptor });
}
@@ -1328,6 +1339,10 @@ public class TypeRegistry {
if (reloadableType == null) {
reloadableType = searchForReloadableType(typeId, typeRegistry);
}
// Check 2: Info computed earlier
// if (!reloadableType.isAffectedByReload()) {
// return false;
// }
// Search for the dispatcher we can call
__DynamicallyDispatchable o = (__DynamicallyDispatchable) invokespecialSearch(reloadableType, nameAndDescriptor);
return o;
@@ -1388,6 +1403,10 @@ public class TypeRegistry {
if (reloadableType == null) {
reloadableType = searchForReloadableType(typeId, typeRegistry);
}
// Check 2: Info computed earlier
if (reloadableType!=null && !reloadableType.isAffectedByReload()) {
return false;
}
if (reloadableType != null && reloadableType.hasBeenReloaded()) {
MethodMember method = reloadableType.getLiveVersion().incrementalTypeDescriptor
.getFromLatestByDescriptor(nameAndDescriptor);
@@ -1567,6 +1586,7 @@ public class TypeRegistry {
*/
@UsedByGeneratedCode
public static boolean ivicheck(int ids, String nameAndDescriptor) {
// Check 1: FAST: Has anything at all been reloaded?
if (nothingReloaded) {
return false;
}
@@ -1574,13 +1594,14 @@ public class TypeRegistry {
// log.entering("TypeRegistry", "ivicheck", new Object[] { ids, nameAndDescriptor });
// }
// TODO [perf] global check (anything been reloaded?)
// TODO [perf] global check (anything been reloaded?)
// TODO [perf] local check (type or anything in its hierarchy reloaded)
int registryId = ids >>> 16;
int typeId = ids & 0xffff;
TypeRegistry typeRegistry = registryInstances[registryId].get();
ReloadableType reloadableType = typeRegistry.getReloadableType(typeId);
// Ok, think about what null means here. It means this registry has not loaded this type as a reloadable type. That doesn't
// mean it isn't reloadable as a parent loaded may have found it. We have 3 options:
@@ -1597,6 +1618,11 @@ public class TypeRegistry {
reloadableType = searchForReloadableType(typeId, typeRegistry);
}
// Check 2: Info computed earlier
if (reloadableType!=null && !reloadableType.isAffectedByReload()) {
return false;
}
if (reloadableType != null && reloadableType.hasBeenReloaded()) {
MethodMember method = reloadableType.getLiveVersion().incrementalTypeDescriptor
.getFromLatestByDescriptor(nameAndDescriptor);
@@ -1665,6 +1691,7 @@ public class TypeRegistry {
if (GlobalConfiguration.verboseMode && log.isLoggable(Level.INFO)) {
log.info("<TypeRegistry.getReloadableType(typeRegistryId=" + typeRegistryId + ",typeId=" + typeId + ") returning " + reloadableType);
}
reloadableType.createTypeAssociations();
return reloadableType;
}
@@ -2007,4 +2034,30 @@ public class TypeRegistry {
this.bsmArgs = bsmArgs;
}
}
}
/**
* Called from the static initializer of a reloadabletype, allowing it to connect
* itself to the parent type, such that when reloading occurs we can mark all
* relevant types in the hierarchy as being impacted by the reload.
*
* @param child the ReloadableType actively being initialized
* @param parent the superclass of the reloadable type (may or may not be reloadable!)
*/
@UsedByGeneratedCode
public static void associateReloadableType(ReloadableType child, Class<?> parent) {
// TODO performance - can we make this cheaper?
ClassLoader parentClassLoader = parent.getClassLoader();
if (parentClassLoader == null) {
return;
}
TypeRegistry parentTypeRegistry = TypeRegistry.getTypeRegistryFor(parent.getClassLoader());
ReloadableType parentReloadableType = parentTypeRegistry.getReloadableType(parent);
if (parentReloadableType != null) {
parentReloadableType.recordSubtype(child);
}
}
}

View File

@@ -26,6 +26,7 @@ import org.objectweb.asm.FieldVisitor;
import org.objectweb.asm.Label;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;
import org.springsource.loaded.Utils.ReturnType;
@@ -764,7 +765,10 @@ public class TypeRewriter implements Constants {
class ClinitPrepender implements Prepender, Constants {
MethodVisitor mv;
private final static String descriptorFor_getReloadableType = "(II)"+lReloadableType;
private final static String descriptorFor_associateReloadableType = "("+lReloadableType+"Ljava/lang/Class;)V";
ClinitPrepender(MethodVisitor mv) {
this.mv = mv;
}
@@ -775,12 +779,13 @@ public class TypeRewriter implements Constants {
// TODO optimization: could collapse ints into one but this snippet isn't put in many places
mv.visitLdcInsn(rtype.getTypeRegistryId());
mv.visitLdcInsn(rtype.getId());
mv.visitMethodInsn(INVOKESTATIC, tRegistryType, "getReloadableType", "(II)" + lReloadableType);
mv.visitMethodInsn(INVOKESTATIC, tRegistryType, "getReloadableType", descriptorFor_getReloadableType, false);
mv.visitFieldInsn(PUTSTATIC, slashedname, fReloadableTypeFieldName, lReloadableType);
// mv.visitFieldInsn(GETSTATIC, slashedname, fReloadableTypeFieldName, lReloadableType);
// mv.visitLdcInsn(Type.getObjectType(rtype.getSlashedSupertypeName()));//Type("L" + rtype.getSlashedSupertypeName() + ";")); // faster way?
// mv.visitMethodInsn(INVOKEVIRTUAL, tReloadableType, "setSuperclass", "(Ljava/lang/Class;)V");
// only in the top most type - what about interfaces??
if (GlobalConfiguration.fieldRewriting) {
mv.visitFieldInsn(GETSTATIC, slashedname, fStaticFieldsName, lStaticStateManager);
@@ -795,14 +800,14 @@ public class TypeRewriter implements Constants {
// If the static initializer has changed, call the new version through the ___clinit___ method
mv.visitFieldInsn(Opcodes.GETSTATIC, slashedname, fReloadableTypeFieldName, lReloadableType);
mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, tReloadableType, "clinitchanged", "()I");
mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, tReloadableType, "clinitchanged", "()I", false);
// 2. Create the if statement
Label wasZero = new Label();
mv.visitJumpInsn(Opcodes.IFEQ, wasZero); // if == 0, jump to where we can do the original thing
// 3. grab the latest dispatcher and call it through the interface
mv.visitFieldInsn(Opcodes.GETSTATIC, slashedname, fReloadableTypeFieldName, lReloadableType);
mv.visitMethodInsn(INVOKEVIRTUAL, tReloadableType, "fetchLatest", "()Ljava/lang/Object;");
mv.visitMethodInsn(INVOKEVIRTUAL, tReloadableType, "fetchLatest", "()Ljava/lang/Object;", false);
mv.visitTypeInsn(CHECKCAST, Utils.getInterfaceName(slashedname));
mv.visitMethodInsn(INVOKEINTERFACE, Utils.getInterfaceName(slashedname), mStaticInitializerName, "()V");
mv.visitInsn(RETURN);

View File

@@ -71,7 +71,8 @@ public class ReloadableTypeMethodProvider extends TypeDescriptorMethodProvider {
@Override
public List<Invoker> getDeclaredMethods() {
if (TypeRegistry.nothingReloaded && rtype.invokersCache_getDeclaredMethods != null) {
if (rtype.invokersCache_getDeclaredMethods != null) {
// if (TypeRegistry.nothingReloaded && rtype.invokersCache_getDeclaredMethods != null) {
// use the cached version, it will not change if a reload hasn't occurred
return rtype.invokersCache_getDeclaredMethods;
}
@@ -82,8 +83,8 @@ public class ReloadableTypeMethodProvider extends TypeDescriptorMethodProvider {
@Override
public Collection<Invoker> getMethods() {
if (TypeRegistry.nothingReloaded && rtype.invokersCache_getMethods != null) {
// use the cached version, it will not change if a reload hasn't occurred
// if ((TypeRegistry.nothingReloaded || !rtype.isAffectedByReload()) &&
if (rtype.invokersCache_getMethods != null) {
return rtype.invokersCache_getMethods;
}
Collection<Invoker> invokers = super.getMethods();