JDK-6805864 : Problem with jvmti->redefineClasses: some methods don't get redefined
  • Type: Bug
  • Component: hotspot
  • Sub-Component: jvmti
  • Affected Version: hs10,hs11,hs14
  • Priority: P3
  • Status: Closed
  • Resolution: Fixed
  • OS: generic,linux,solaris_10
  • CPU: generic,x86
  • Submitted: 2009-02-16
  • Updated: 2013-02-01
  • Resolved: 2011-03-08
The Version table provides details related to the release that this issue/RFE will be addressed.

Unresolved : Release in which this issue/RFE will be addressed.
Resolved: Release in which this issue/RFE has been resolved.
Fixed : Release in which this issue/RFE has been fixed. The release containing this fix may be available for download as an Early Access Release or a General Availability Release.

To download the current JDK release, click here.
JDK 6 JDK 7 Other
6u14Fixed 7Fixed hs14Fixed
Related Reports
Relates :  
Relates :  
Description of the problem:

We found a method where redefineClasses doesn't work as expected.

If we instrument a method during the ClassFileLoadHook, the transformed method is used. After we uninstrumented the method using redefineClasses and call the method, the old(instrumented) method is used.

We first thought that there are problems unjitting the method, but the problem occurred with -Xint too. All 1.6.0 VMs are affected, even jdk 1.6.0_11 (1.5 VMs haven't been tested).

A simple reproducer with a launch script for linux is attached.

The reproducer exchange the
org.hibernate.persister.entity.AbstractEntityPersister class during the ClassFileLoadHook with a version where the isVersioned method print its name. 
Methods from this class get called every two seconds. After 5 seconds the class is redefined with a version where another method print its name, and the isVersioned method is uninstrumented. If these methods are called, hasCascades prints hasCascades - as expected - but isVersioned also prints isVersioned.
I added support to the TraceRedefineClasses option to catch
the case where an obsolete method is being called. Here are
snippets of the info gathered:

RedefineClasses-0x1000: calling obsolete method 'org.hibernate.persister.entity.AbstractEntityPersister.isVersioned()Z'

#  Internal Error (src/share/vm/runtime/sharedRuntime.cpp:413)
#  Error: guarantee(false,"should never call an obsolete method.")

Here is the stack trace from a decoded hs_err_pid file:

V  [libjvm.so+0x92812f];;  __1cNSharedRuntimeVrc_trace_method_entry6FpnKJavaThread_pnNmethodOopDesc__i_+0x263
j  org.hibernate.persister.entity.AbstractEntityPersister.isVersioned()Z+0
j  org.hibernate.event.def.AbstractSaveEventListener.substituteValuesIfNecessary(Ljava/lang/Object;Ljava/io/Serializable;[Ljava/lang/Object;Lorg/hibernate/persister/entity/EntityPersister;Lorg/hibernate/engine/SessionImplementor;)Z+33
j  org.hibernate.event.def.AbstractSaveEventListener.performSaveOrReplicate(Ljava/lang/Object;Lorg/hibernate/engine/EntityKey;Lorg/hibernate/persister/entity/EntityPersister;ZLjava/lang/Object;Lorg/hibernate/event/EventSource;Z)Ljava/io/Serializable;+155
j  org.hibernate.event.def.AbstractSaveEventListener.performSave(Ljava/lang/Object;Ljava/io/Serializable;Lorg/hibernate/persister/entity/EntityPersister;ZLjava/lang/Object;Lorg/hibernate/event/EventSource;Z)Ljava/io/Serializable;+202
j  org.hibernate.event.def.AbstractSaveEventListener.saveWithGeneratedId(Ljava/lang/Object;Ljava/lang/String;Ljava/lang/Object;Lorg/hibernate/event/EventSource;Z)Ljava/io/Serializable;+193
j  org.hibernate.event.def.DefaultPersistEventListener.entityIsTransient(Lorg/hibernate/event/PersistEvent;Ljava/util/Map;)V+55
j  org.hibernate.event.def.DefaultPersistEventListener.onPersist(Lorg/hibernate/event/PersistEvent;Ljava/util/Map;)V+180
j  org.hibernate.event.def.DefaultPersistEventListener.onPersist(Lorg/hibernate/event/PersistEvent;)V+7
j  org.hibernate.impl.SessionImpl.firePersist(Lorg/hibernate/event/PersistEvent;)V+28
j  org.hibernate.impl.SessionImpl.persist(Ljava/lang/String;Ljava/lang/Object;)V+11
j  org.hibernate.impl.SessionImpl.persist(Ljava/lang/Object;)V+3
j  Misc.urks(Lorg/hibernate/Session;)V+15
j  Misc.main([Ljava/lang/String;)V+77
v  ~StubRoutines::call_stub
V  [libjvm.so+0x4821c7];;  __1cJJavaCallsLcall_helper6FpnJJavaValue_pnMmethodHandle_pnRJavaCallArguments_pnGThread__v_+0x6c3

I'll take a look at org.hibernate.event.def.AbstractSaveEventListener.
substituteValuesIfNecessary() to see how it is calling
That might tell me if we missed something during redefinition.
The call in org.hibernate.event.def.AbstractSaveEventListener.
substituteValuesIfNecessary() looks like this:

   31:  aload   4
   33:  invokeinterface #488,  1; //InterfaceMethod org/hibernate/persister/entity/EntityPersister.isVersioned:()Z
   38:  ifeq    76

I remember that TraceRedefineClasses output told me that there
were itable updates for isVersioned(), but I need to take a
closer look.
I've attached the reproducer.tgz file provided by the customer.
I've attached my initial draft of RedefineAbstractClass.sh to
this bug. This version of the test was written relative to
JDK6 so I'll have to tweak it for JDK7.

EVALUATION http://hg.openjdk.java.net/jdk7/hotspot-rt/hotspot/rev/70998f2e05ef

SUGGESTED FIX See the attached 6805864-webrev-cr0.tgz for the proposed fix.

EVALUATION In the provided test case, the target class (org.hibernate.persister.entity.AbstractEntityPersister) is an abstract class that implements more than one interface. The isVersioned() method is also specified by more than interface. So the isVersioned() method that is defined by the org.hibernate.persister.entity.AbstractEntityPersister abstract class applies to more than one interface so it appears in more than one itable entry for the abstract class org.hibernate.persister.entity.AbstractEntityPersister. The RedefineClasses() implementation does not expect more than one itable entry to contain a matching method when it is updating the itable for a target class. I'm generating a small test case to illustrate the bug. This test will be added to the JDI_REGRESSION test suite.