From 8c49ba38223a5c861573a6d3034869bf77087c50 Mon Sep 17 00:00:00 2001 From: Andy Clement Date: Tue, 20 May 2014 11:29:23 -0700 Subject: [PATCH] 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?' --- .../springsource/loaded/ReloadableType.java | 146 +++++++++++++++++- .../org/springsource/loaded/TypeRegistry.java | 57 ++++++- .../org/springsource/loaded/TypeRewriter.java | 15 +- .../ri/ReloadableTypeMethodProvider.java | 7 +- .../src/main/java/subassociatedtypes/CB.java | 7 + .../src/main/java/subassociatedtypes/IB.java | 5 + .../src/main/java/subassociatedtypes/IB2.java | 5 + .../src/main/java/associatedtypes/CM.java | 6 + .../src/main/java/associatedtypes/CT.java | 5 + .../src/main/java/associatedtypes/IM.java | 5 + .../src/main/java/associatedtypes/IM2.java | 5 + .../src/main/java/associatedtypes/IT.java | 5 + .../src/main/java/associatedtypes/IT2.java | 5 + 13 files changed, 259 insertions(+), 14 deletions(-) create mode 100644 testdata-subloader/src/main/java/subassociatedtypes/CB.java create mode 100644 testdata-subloader/src/main/java/subassociatedtypes/IB.java create mode 100644 testdata-subloader/src/main/java/subassociatedtypes/IB2.java create mode 100644 testdata-superloader/src/main/java/associatedtypes/CM.java create mode 100644 testdata-superloader/src/main/java/associatedtypes/CT.java create mode 100644 testdata-superloader/src/main/java/associatedtypes/IM.java create mode 100644 testdata-superloader/src/main/java/associatedtypes/IM2.java create mode 100644 testdata-superloader/src/main/java/associatedtypes/IT.java create mode 100644 testdata-superloader/src/main/java/associatedtypes/IT2.java diff --git a/springloaded/src/main/java/org/springsource/loaded/ReloadableType.java b/springloaded/src/main/java/org/springsource/loaded/ReloadableType.java index 2d55711..93423ae 100644 --- a/springloaded/src/main/java/org/springsource/loaded/ReloadableType.java +++ b/springloaded/src/main/java/org/springsource/loaded/ReloadableType.java @@ -95,11 +95,17 @@ public class ReloadableType { private Class superclazz; private ReloadableType superRtype; + private ReloadableType[] interfaceRtypes; + + List> 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 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 reloadableInterfaces = new ArrayList(); + 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>(); + } + associatedSubtypes.add(new WeakReference(child)); + if (this.isAffectedByReload()) { + child.tagAsAffectedByReload(); + child.tagSubtypesAsAffectedByReload(); + } + } + + public List> 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); + } + } + } } diff --git a/springloaded/src/main/java/org/springsource/loaded/TypeRegistry.java b/springloaded/src/main/java/org/springsource/loaded/TypeRegistry.java index 3f3b2ad..36fea19 100644 --- a/springloaded/src/main/java/org/springsource/loaded/TypeRegistry.java +++ b/springloaded/src/main/java/org/springsource/loaded/TypeRegistry.java @@ -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(" 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); + } + } + +} + + + diff --git a/springloaded/src/main/java/org/springsource/loaded/TypeRewriter.java b/springloaded/src/main/java/org/springsource/loaded/TypeRewriter.java index c39b4e9..8f77aae 100644 --- a/springloaded/src/main/java/org/springsource/loaded/TypeRewriter.java +++ b/springloaded/src/main/java/org/springsource/loaded/TypeRewriter.java @@ -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); diff --git a/springloaded/src/main/java/org/springsource/loaded/ri/ReloadableTypeMethodProvider.java b/springloaded/src/main/java/org/springsource/loaded/ri/ReloadableTypeMethodProvider.java index 11e9a60..a29a41e 100644 --- a/springloaded/src/main/java/org/springsource/loaded/ri/ReloadableTypeMethodProvider.java +++ b/springloaded/src/main/java/org/springsource/loaded/ri/ReloadableTypeMethodProvider.java @@ -71,7 +71,8 @@ public class ReloadableTypeMethodProvider extends TypeDescriptorMethodProvider { @Override public List 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 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 invokers = super.getMethods(); diff --git a/testdata-subloader/src/main/java/subassociatedtypes/CB.java b/testdata-subloader/src/main/java/subassociatedtypes/CB.java new file mode 100644 index 0000000..03d734e --- /dev/null +++ b/testdata-subloader/src/main/java/subassociatedtypes/CB.java @@ -0,0 +1,7 @@ +package subassociatedtypes; + +import associatedtypes.CM; + +public class CB extends CM implements IB,IB2 { + +} diff --git a/testdata-subloader/src/main/java/subassociatedtypes/IB.java b/testdata-subloader/src/main/java/subassociatedtypes/IB.java new file mode 100644 index 0000000..52a724d --- /dev/null +++ b/testdata-subloader/src/main/java/subassociatedtypes/IB.java @@ -0,0 +1,5 @@ +package subassociatedtypes; + +public interface IB { + +} diff --git a/testdata-subloader/src/main/java/subassociatedtypes/IB2.java b/testdata-subloader/src/main/java/subassociatedtypes/IB2.java new file mode 100644 index 0000000..7482e49 --- /dev/null +++ b/testdata-subloader/src/main/java/subassociatedtypes/IB2.java @@ -0,0 +1,5 @@ +package subassociatedtypes; + +public interface IB2 { + +} diff --git a/testdata-superloader/src/main/java/associatedtypes/CM.java b/testdata-superloader/src/main/java/associatedtypes/CM.java new file mode 100644 index 0000000..7b30aad --- /dev/null +++ b/testdata-superloader/src/main/java/associatedtypes/CM.java @@ -0,0 +1,6 @@ +package associatedtypes; + +public class CM extends CT implements IM,IM2 { + + public static void run() {} +} diff --git a/testdata-superloader/src/main/java/associatedtypes/CT.java b/testdata-superloader/src/main/java/associatedtypes/CT.java new file mode 100644 index 0000000..9aa77dc --- /dev/null +++ b/testdata-superloader/src/main/java/associatedtypes/CT.java @@ -0,0 +1,5 @@ +package associatedtypes; + +public class CT implements IT, IT2 { + +} diff --git a/testdata-superloader/src/main/java/associatedtypes/IM.java b/testdata-superloader/src/main/java/associatedtypes/IM.java new file mode 100644 index 0000000..c7851b5 --- /dev/null +++ b/testdata-superloader/src/main/java/associatedtypes/IM.java @@ -0,0 +1,5 @@ +package associatedtypes; + +public interface IM { + +} diff --git a/testdata-superloader/src/main/java/associatedtypes/IM2.java b/testdata-superloader/src/main/java/associatedtypes/IM2.java new file mode 100644 index 0000000..522ddff --- /dev/null +++ b/testdata-superloader/src/main/java/associatedtypes/IM2.java @@ -0,0 +1,5 @@ +package associatedtypes; + +public interface IM2 { + +} diff --git a/testdata-superloader/src/main/java/associatedtypes/IT.java b/testdata-superloader/src/main/java/associatedtypes/IT.java new file mode 100644 index 0000000..2bfa467 --- /dev/null +++ b/testdata-superloader/src/main/java/associatedtypes/IT.java @@ -0,0 +1,5 @@ +package associatedtypes; + +public interface IT { + +} diff --git a/testdata-superloader/src/main/java/associatedtypes/IT2.java b/testdata-superloader/src/main/java/associatedtypes/IT2.java new file mode 100644 index 0000000..e4e55a2 --- /dev/null +++ b/testdata-superloader/src/main/java/associatedtypes/IT2.java @@ -0,0 +1,5 @@ +package associatedtypes; + +public interface IT2 { + +}