Question regarding JVM crashes in GC

Volker Simonis volker.simonis at gmail.com
Mon Apr 10 18:14:17 UTC 2017


Hi Michael,

I think the problem is the following:

In normal operation (i.e. without your agent) classes get loaded in the
following order:

...
[0.046s][info][class,load] java.lang.ClassLoader source: jrt:/java.base
[0.046s][info][class,load] java.lang.System source: jrt:/java.base
...
[0.047s][info][class,load] java.lang.ref.Reference source: jrt:/java.base
[0.047s][info][class,load] java.lang.ref.SoftReference source:
jrt:/java.base
[0.047s][info][class,load] java.lang.ref.WeakReference source:
jrt:/java.base
[0.047s][info][class,load] java.lang.ref.FinalReference source:
jrt:/java.base
[0.047s][info][class,load] java.lang.ref.PhantomReference source:
jrt:/java.base

This order is specified in systemDictionary.hpp:

// The order of these definitions is significant; it is the order in which
// preloading is actually performed by initialize_preloaded_classes.

#define WK_KLASSES_DO(do_klass)
                                             \
  /* well-known classes */
                                              \
  do_klass(Object_klass,                                java_lang_Object,
                       Pre                 ) \
  do_klass(String_klass,                                java_lang_String,
                       Pre                 ) \
  do_klass(Class_klass,                                 java_lang_Class,
                        Pre                 ) \
  do_klass(Cloneable_klass,
java_lang_Cloneable,                       Pre                 ) \
  do_klass(ClassLoader_klass,
java_lang_ClassLoader,                     Pre                 ) \
  do_klass(Serializable_klass,
 java_io_Serializable,                      Pre                 ) \
  do_klass(System_klass,                                java_lang_System,
                       Pre                 ) \
...
  do_klass(Reference_klass,
java_lang_ref_Reference,                   Pre                 ) \
  /* Preload ref klasses and set reference types */
                                             \
  do_klass(SoftReference_klass,
java_lang_ref_SoftReference,               Pre                 ) \
  do_klass(WeakReference_klass,
java_lang_ref_WeakReference,               Pre                 ) \
  do_klass(FinalReference_klass,
 java_lang_ref_FinalReference,              Pre                 ) \
  do_klass(PhantomReference_klass,
 java_lang_ref_PhantomReference,            Pre                 ) \

The method 'SystemDictionary::initialize_preloaded_classes(TRAPS)' in
systemDictionary.cpp not only loads the classes in this order, it also does
some initialization stuff in between (i.e. specific initializations after
some specific class have been loaded):

   initialize_wk_klasses_through(WK_KLASS_ENUM_NAME(Reference_klass), scan,
CHECK);

  // Preload ref klasses and set reference types

InstanceKlass::cast(WK_KLASS(Reference_klass))->set_reference_type(REF_OTHER);
  InstanceRefKlass::update_nonstatic_oop_maps(WK_KLASS(Reference_klass));

  initialize_wk_klasses_through(WK_KLASS_ENUM_NAME(PhantomReference_klass),
scan, CHECK);

InstanceKlass::cast(WK_KLASS(SoftReference_klass))->set_reference_type(REF_SOFT);

InstanceKlass::cast(WK_KLASS(WeakReference_klass))->set_reference_type(REF_WEAK);

InstanceKlass::cast(WK_KLASS(FinalReference_klass))->set_reference_type(REF_FINAL);

InstanceKlass::cast(WK_KLASS(PhantomReference_klass))->set_reference_type(REF_PHANTOM);

As you can see, first all the classes up to java.lang.ref.Reference are
loaded. Then the reference type of java.lang.ref.Reference is set to
"REF_OTHER". After that the other reference class (from SoftReference up to
PhantomReference) are loaded and their corresponding reference type is set.

For all the other classes which get loaded later, the reference type will
be set to the reference type of their parent class (see
'ClassFileParser::post_process_parsed_stream()' in classFileParser.cpp):

  // Compute reference typ
  _rt = (NULL ==_super_klass) ? REF_NONE : _super_klass->reference_type();

So when you derive a class from WeakReference, its reference type will be
"REF_WEAK".

Now with your agent, you change the loading of classes as follows:

...
[0.046s][info][class,load] java.lang.ClassLoader source: jrt:/java.base
[0.046s][info][class,load] java.lang.ref.Reference source: jrt:/java.base
[0.046s][info][class,load] java.lang.ref.WeakReference source:
jrt:/java.base
[0.046s][info][class,load] java.lang.ref.MyWeakReference
[0.047s][info][class,load] java.lang.System source: jrt:/java.base
...
[0.048s][info][class,load] java.lang.ref.SoftReference source:
jrt:/java.base
[0.048s][info][class,load] java.lang.ref.FinalReference source:
jrt:/java.base
[0.048s][info][class,load] java.lang.ref.PhantomReference source:
jrt:/java.base
...

This means that your class 'java.lang.ref.MyWeakReference' will get
reference type "REF_NONE", because you implicitly triggered the loading of
WeakReference and Reference without updating their reference type
accordingly. Later on in the game, the reference type of WeakReference and
Reference will be corrected, but your custom reference class will remain
with its initial reference type "REF_NONE".

I think this confuses the GC in the end and leads to the crashes observed
by you. Funny enough, I don't see any crash with a product build of the
latest jdk9-dev sources on Linux, but I can reproduce "should be in heap"
assertion observed by you:

#  Internal Error
(/usr/work/d046063/OpenJDK/jdk9-dev/hotspot/src/share/vm/gc/serial/markSweep.inline.hpp:53),
pid=18985, tid=19058
#  assert(Universe::heap()->is_in(obj)) failed: should be in heap

So to keep a long story short, I don't think it is a good idea to change
the class loading and initialization order of the "well-known" classes as
described in systemDictionary.hpp. That order is carefully handcrafted and
extremely sensitive to changes and can have all kind of unforeseeable side
effects (as you've painfully detected yourself :)

Regards,
Volker


On Mon, Apr 10, 2017 at 5:37 PM, Michael Rasmussen <
michael.rasmussen at zeroturnaround.com> wrote:

> On 10 April 2017 at 17:48, Stefan Johansson <stefan.johansson at oracle.com>
> wrote:
> > This sounds great. Could you attach the test case to the mailing list in
> > case we want to add this to the OpenJDK testing going forward?
>
> Attached inline below
>
> /Michael
>
> --- agent.c ---
>
> #include <string.h>
>
> #include "jni.h"
> #include "jvmti.h"
>
> /*
>   public class java.lang.ref.MyWeakReference
>     extends java.lang.ref.WeakReference {};
> */
> jbyte MyWeakReference_bytes[] = {
>   -54,  -2, -70, -66,   0,   0,   0,  49,
>     0,   5,   1,   0,  29, 106,  97, 118,
>    97,  47, 108,  97, 110, 103,  47, 114,
>   101, 102,  47,  77, 121,  87, 101,  97,
>   107,  82, 101, 102, 101, 114, 101, 110,
>    99, 101,   7,   0,   1,   1,   0,  27,
>   106,  97, 118,  97,  47, 108,  97, 110,
>   103,  47, 114, 101, 102,  47,  87, 101,
>    97, 107,  82, 101, 102, 101, 114, 101,
>   110,  99, 101,   7,   0,   3,   0,   1,
>     0,   2,   0,   4,   0,   0,   0,   0,
>     0,   0,   0,   0
> };
> jsize MyWeakReference_len = 0x5C;
>
> void JNICALL callback_ClassFileLoadHook(jvmtiEnv *jvmti, JNIEnv* env,
>   jclass class_being_redefined, jobject loader, const char* name,
>   jobject protection_domain,
>   jint class_data_len, const unsigned char* class_data,
>   jint* new_class_data_len, unsigned char** new_class_data) {
>
>   if (name && !strcmp(name, "java/lang/System")) {
>     (*env)->DefineClass(env, "java/lang/ref/MyWeakReference", NULL,
>       MyWeakReference_bytes, MyWeakReference_len);
>   }
> }
>
> JNIEXPORT jint JNICALL Agent_OnLoad(JavaVM *vm,
>   char *options, void *reserved) {
>
>   jvmtiEventCallbacks callbacks;
>   jvmtiCapabilities caps;
>   jvmtiEnv *jvmti;
>
>   (*vm)->GetEnv(vm, (void **)&jvmti, JVMTI_VERSION_9);
>
>   memset(&caps, 0, sizeof(caps));
>   memset(&callbacks, 0, sizeof(callbacks));
>
>   caps.can_generate_early_vmstart = 1;
>   caps.can_generate_all_class_hook_events = 1;
>   caps.can_generate_early_class_hook_events = 1;
>
>   callbacks.ClassFileLoadHook = &callback_ClassFileLoadHook;
>
>   (*jvmti)->AddCapabilities(jvmti, &caps);
>   (*jvmti)->SetEventCallbacks(jvmti, &callbacks, sizeof(callbacks));
>   (*jvmti)->SetEventNotificationMode(jvmti, JVMTI_ENABLE,
>     JVMTI_EVENT_CLASS_FILE_LOAD_HOOK, NULL);
>
>   return 0;
> }
>
> --- MiscTest9.java ---
>
> package app1;
>
> import java.lang.ref.WeakReference;
>
> public class MiscTest9 {
>   public static void main(String[] args) throws Exception {
>     WeakReference<?> weak = new WeakReference<>(new MiscTest9());
>
>     boolean present = true;
>     while (present) {
>       System.out.println("Running GC");
>       System.gc();
>       present = weak.get() != null;
>     }
>   }
> }
>


More information about the hotspot-dev mailing list