Skip to content

java.lang.Class instances

Andrew Binstock edited this page Apr 6, 2026 · 2 revisions

Instances of java/lang/Class (JLC) are well-known to Java developers in reflection. However, inside the JVM, they have other uses that are not immediately apparent. This page discusses uses of the JLC in detail, with occasional explanations of how the details are implemented in Jacobin.

What are JLCs?

Contrary to the impression the reflection API might convey, JLCs are skinny classes that don't contain metadata on all the fields and methods of a class. Rather they contain a pointer, named _klass, which points back to the class metadata. JLCs also contain all the static fields of a class. (Recall that static fields are unique and shared by all running instances of a class. The JLC is the container holding them.) As a result, the JVM creates eagerly a unique JLC for every loaded class. In Jacobin, the JLC is created at the time a class is loaded.

Primitives: the special case

Primitives in Java (i.e., boolean, char, int, etc.) are not objects. However, all primitives have boxing classes that can wrap them for situations in which an object is required. These boxing classes are java/lang/Boolean, java/lang/Char, and so on. (They also include java/lang/Void, which boxes a void--a curious invention since void is neither an object nor a primitive.) The boxed classes have a TYPE field, which identifies what kind of primitive they are boxing. That field points to a JLC. As a result, the JVM creates JLCs for all eight primitives, plus void. These JLCs have no corresponding class (they're internal _klass pointer is null). As a result, those JLCs need to be created and loaded separate from any class loading operation.

How do JLCs work with reflection?

When reflection is used, the first call is generally getClass(), which returns a pointer to the JLC for the current object. Then as methods in the reflection API are called on the object, they use the _klass pointer to access class metadata and they also fold in the values of any of the instance fields. Between those two sources of data, the reflection API creates the impression that you're directly affecting an object, whereas the access is distinctly indirect.

JVM's use of JLCs

JLCs also play a role in concurrency. Every object has a header that contains a lock. When a synchronized method is called, the JVM locks this lock on the object. The JVM unlocks it when the method returns or in the event of an exception (the details of the latter are somewhat more complicated than represented here). However, when a static synchronized method is called, there is no instance object to lock. For example, java/util/TimeZone.setDefault() sets the default timezone for all instances of TimeZone. It is synchronized in the event that two threads both attempt to set the default. There is, however, no TimeZone object on which this setting is done. So these methods use the lock of a unique object: the class's corresponding JLC.

Clone this wiki locally