micartey
micartey

Reputation: 174

Java ClassLoader loading class only for its own context?

I have some strange edge case behavior I want to discuss and solve with you. And as a heads-up: Please do not ask why I want to do something :)

As far as my understanding goes, the JVM loads classes into the heap once they are needed (or forced to load) and once they are loaded, they stay there... That's also the reason why you have to overwrite bytecode though instrumentation to change e.g. a method of a class at runtime once it's loaded.

Now to my problem:

I have written a custom ClassLoader that works fine and is also able to load the class. I verified that by running:

CustomClassLoader classLoader = new CustomClassLoader();
Class<?> clazz = classLoader.loadClass("me.micartey.test.Core");
System.out.println(clazz);
Core core = new Core();

The output is following:

class me.micartey.test.core.Core
java.lang.NoClassDefFoundError: me/micartey/test/core/Core ...

So my suspicion is that classloader may not "share" all classes in the heap... :thinking: (Reflection works but not my goal)

I then tried to set the parent of the current classloader as the parent of my custom classloader and then my custom classloader as the parent of the current classloader. However, this results in a StackOverflowException when trying to load classes that my classloader uses

Preferably, I don't even need to call loadClass and it is able to resolve the class without any reflection etc.

Upvotes: 1

Views: 1223

Answers (1)

Holger
Holger

Reputation: 298459

Classes are not visible across the entire JVM and class loaders are not omnipotent entities knowing every existing class in the JVM. That’s why they have a parent loader to delegate to. Different class loaders may even define (unrelated) classes with the same name.

As the JVM specification puts it:

After creation, a class or interface is determined not by its name alone, but by a pair: its binary name (§4.2.1) and its defining loader.

Also, the assumption that classes always stay forever is not correct. If a (custom) class loader and all of its defined classes become unreachable, they might get garbage collected.

From the Java Language Specification:

An implementation of the Java programming language may unload classes.

A class or interface may be unloaded if and only if its defining class loader may be reclaimed by the garbage collector as discussed in §12.6.

So your current class loader can not see the classes defined by your custom class loader. You can not reverse the parent relationship of these two loaders when the implementation of your custom class loader is defined by your current loader.

If you want to inject the definition of a class into your current class loading context, the simplest approach is

MethodHandles.lookup().defineClass(bytecode);

where bytecode is a byte[] array containing the definiton in the class file format, just like your custom class loader would need.

See also the API of MethodHandles.lookup() and MethodHandles.Lookup.defineClass(…).

To be able to use Core core = new Core(); in the source code after this statement, there must be a definition of the class available at compile-time, but that’s not different to your class loader approach.

You have to be aware of the potential problems of the class not being available before executing the statement that injects it. Consider “When is a Java Class loaded?” and also Does the JVM throw if an unused class is absent? for a practical example

Upvotes: 4

Related Questions