kosta5
kosta5

Reputation: 1138

Access classLoader field via reflection

We have an application with custom classloader and I need to access classLoader field on given classes. However this field is not accessible via reflection :-(

The JavaDoc for java.lang.Class is clear:

// This field is filtered from reflection access, i.e. getDeclaredField
// will throw NoSuchFieldException

So this is what I get when calling getDeclaredField("classLoader") Can this be obtained somehow (I see IntelliJ debugging does that somehow; how?)

Maybe some byteBuddy trickery?

Upvotes: 3

Views: 1927

Answers (5)

kosta5
kosta5

Reputation: 1138

This is not an answer to the actual question but addresses the root cause why this question was asked. Our symptoms (motivations why I asked this)

  • We use custom classloading extensively (tens of Classloaders created every minute that "come and go")

  • GC was not collecting those classloaders "fast enough" even if they had no direct references based on heapdump analysis. Only quite a few WeakReferences

  • Classloaders not being GCed over long period of time (we had closed classloaders that were still present in heap hours after they got closed) caused drastic metaspace expansion (3G+)

Silver bullet that fixed this was setting a JVM flag that forces full GC more often than it naturally occurs.

===> -XX:MaxMetaspaceExpansion <=== (in our case -XX:MaxMetaspaceExpansion=8M

Some more reading can be found e.g. here: What is the Metadata GC Threshold and how do I tune it?

Upvotes: 0

GotoFinal
GotoFinal

Reputation: 3675

There is much simpler way than any provided answer here, but might not work on all JVM vendors: you can skip filtering by just invoking raw native method:

    Method getDeclaredFields0 = Class.class.getDeclaredMethod("getDeclaredFields0", boolean.class);
    getDeclaredFields0.setAccessible(true);
    Field[] unfilteredFields = (Field[]) getDeclaredFields0.invoke(Class.class, false);

And then you can just iterate that array to find your field.

You can use this trick to also completely remove filtering, as its stored in map inside jdk.internal.reflect.Reflection class, but fields are also filtered by default.

private static volatile Map<Class<?>,String[]> fieldFilterMap;
private static volatile Map<Class<?>,String[]> methodFilterMap;

static {
    Map<Class<?>,String[]> map = new HashMap<Class<?>,String[]>();
    map.put(Reflection.class,
        new String[] {"fieldFilterMap", "methodFilterMap"});
    map.put(System.class, new String[] {"security"});
    map.put(Class.class, new String[] {"classLoader"});
    fieldFilterMap = map;

    methodFilterMap = new HashMap<>();
}

Upvotes: 3

apangin
apangin

Reputation: 98324

Java debuggers see this field, because they rely on JDWP, which works on top of native APIs: JNI and JVM TI.

You technically can access/modify classLoader field with JNI or Unsafe, but please don't do this!

After all, why do you think this field is filtered from reflection access? Exactly to prevent people from shooting themselves in the foot by modifying the field.

The key point is that a class should never be separated from its class loader. HotSpot JVM cannot unload classes one by one; instead, it unloads the whole class loader, when no live references to the ClassLoader object remain.

When a ClassLoader object is garbage collected, the corresponding part of the Metaspace can be reclaimed, along with the metadata for all classes loaded by this ClassLoader.

Now, what happens if you null out classLoader field? If there are no more references to the corresponding ClassLoader, it becomes eligible for garbage collection (seems like exactly what you want). But this may trigger class unloading, that will kill all the metadata for classes of this loader. However, classes (and their instances) are completely broken without the metadata. After that, any operation on your class or one of its instances may randomly crash JVM or make the application otherwise unstable.

Upvotes: 3

Rafael Winterhalter
Rafael Winterhalter

Reputation: 44032

To answer your question: You can still hack your way to the field using Byte Buddy by creating a mirror of the class which will have a smiliar class layout such that you can access and modify fields using Unsafe by first creating a mirror of the class that hides fields from reflection:

Class<?> mirror = new ByteBuddy()
    .with(TypeValidation.DISABLED)
    .redefine(Class.class)
    .name("mirror.Class")
    .noNestMate()
    .make()
    .load(null)
    .getLoaded();

Class<?> unsafeType = Class.forName("sun.misc.Unsafe");
Field theUnsafe = unsafeType.getDeclaredField("theUnsafe");
theUnsafe.setAccessible(true);
Object unsafe = theUnsafe.get(null);

long offset = (Long) unsafeType
    .getMethod("objectFieldOffset", Field.class)
    .invoke(unsafe, mirror.getDeclaredField("classLoader"));
ClassLoader loader = (ClassLoader) unsafeType
    .getMethod("getObject", Object.class, long.class)
    .invoke(unsafe, Foo.class, offset - 4);

The mirror has a smiliar field layout as the original class such that you can retain that layout and access fields as you demand it. You can in the same way use putObject to override the field value.

Would I recommend this approach however? Absolutely not. This will stop working in any future version of Java, too. If you need some extra time to work on a proper solution, this might be a way to go but long term, you should refactor your code to make this work-around uneccessary.

Upvotes: 6

mentallurg
mentallurg

Reputation: 5207

It is good that you explained the reason of this question: reducing memory usage. If you manage to detch classes from their class loaders, other problems can occur. For instance, equals() and instanceof would work differently (if at all), deserialization of objects can work differently, etc.

1) I'd suggest you to check what is the real reason of memory consumption: is it the class loaded instance itself or is it one of classes loaded by this class loader? For instance, a class can have some static field that consumes much memory.

2) If the class loader instance consumes much memory, consider using weak reference or a cache for the field that consumes much memory.

3) If you want to try a "nicer way around": Consider Java Agent, transform() or redefineClasses. May be in this way you can add needed behaviour to the classes loaded by your class loaders and simplify your task of eliminating unneeded references and freeing some memory.

Upvotes: 1

Related Questions