1. Overview
Reflection in Java is a powerful feature that allows us to manipulate different members, such as classes, interfaces, fields, and methods. Moreover, using reflection, we can instantiate classes, call methods, and access fields at compile time without knowing the type.
In this tutorial, we’ll first explore the JVM access flags. Then, we’ll see how we can use them. Lastly, we’ll examine the differences between modifiers and access flags.
2. JVM Access Flags
Let’s start by understanding the JVM access flags.
The Java Virtual Machine Specification defines the structure of the compiled class in the JVM, which consists of a single ClassFile:
ClassFile {
u4 magic;
u2 minor_version;
u2 major_version;
u2 constant_pool_count;
cp_info constant_pool[constant_pool_count-1];
u2 access_flags;
u2 this_class;
u2 super_class;
u2 interfaces_count;
u2 interfaces[interfaces_count];
u2 fields_count;
field_info fields[fields_count];
u2 methods_count;
method_info methods[methods_count];
u2 attributes_count;
attribute_info attributes[attributes_count];
}
The ClassFile, among other items, contains the access_flags item. Simply put, the access_flags is a mask that consists of various flags that define access permissions and other properties on a class.
Additionally, the ClassFile consists of the field_info and the method_info items, each containing its access_flags item.
Libraries like Javassist and ASM use the JVM access flags to manipulate Java bytecode.
Apart from Java, JVM supports other languages, such as Kotlin or Scala. Each language has defined its modifiers. For example, the Modifier class in Java contains all modifiers specific to the Java programming language. Moreover, we usually rely on the information retrieved from these classes when working with reflection.
Nevertheless, the problem arises when modifiers need to be transformed into the JVM access flags. Let’s further examine why.
3. AccessFlag for Modifiers
Modifiers such as varargs and transient or volatile and bridge use the same integer bit mask. To fix bit collisions between different modifiers, Java 20 introduced the AccessFlag enum, which includes all modifiers we can use within a class, field, or method.
The enumeration models the JVM access flags to ease mapping between modifiers and access flags. Without the AccessFlag enum, we’d need to consider the element’s context to determine which modifier is used, especially for those with the exact bit representation.
To see AccessFlag in action, let’s create the AccessFlagDemo class with several methods, each using different modifiers:
public class AccessFlagDemo {
public static final void staticFinalMethod() {
}
public void varArgsMethod(String... args) {
}
public strictfp void strictfpMethod() {
}
}
Next, let’s examine the access flags used in the staticFinalMethod() method:
@Test
void givenStaticFinalMethod_whenGetAccessFlag_thenReturnCorrectFlags() throws Exception {
Class<?> clazz = Class.forName(AccessFlagDemo.class.getName());
Method method = clazz.getMethod("staticFinalMethod");
Set<AccessFlag> accessFlagSet = method.accessFlags();
assertEquals(3, accessFlagSet.size());
assertTrue(accessFlagSet.contains(AccessFlag.PUBLIC));
assertTrue(accessFlagSet.contains(AccessFlag.STATIC));
assertTrue(accessFlagSet.contains(AccessFlag.FINAL));
}
Here, we called the accessFlags() method, which returned EnumSet wrapped into an unmodifiable set. Internally, the method uses the getModifiers() method and returns access flags depending on the location where flags can be applied. Our method contains three access flags: PUBLIC, STATIC, and FINAL.
Additionally, as of Java 17, the strictfp modifier is redundant and is no longer compiled into the bytecode:
@Test
void givenStrictfpMethod_whenGetAccessFlag_thenReturnOnlyPublicFlag() throws Exception {
Class<?> clazz = Class.forName(AccessFlagDemo.class.getName());
Method method = clazz.getMethod("strictfpMethod");
Set<AccessFlag> accessFlagSet = method.accessFlags();
assertEquals(1, accessFlagSet.size());
assertTrue(accessFlagSet.contains(AccessFlag.PUBLIC));
}
As we can see, the strictfpMethod() contains a single access flag.
4. getModifiers() vs. accessFlags() Methods
When working with reflection in Java, we often use the getModifiers() method to retrieve all modifiers defined on classes, interfaces, methods, or fields:
@Test
void givenStaticFinalMethod_whenGetModifiers_thenReturnIsStaticTrue() throws Exception {
Class<?> clazz = Class.forName(AccessFlagDemo.class.getName());
Method method = clazz.getMethod("staticFinalMethod");
int methodModifiers = method.getModifiers();
assertEquals(25, methodModifiers);
assertTrue(Modifier.isStatic(methodModifiers));
}
The getModifiers() method returns an integer value representing encoded modifier flags. We called the isStatic() method defined inside the Modifier class to check whether the method contains a static modifier. Additionally, Java decodes flags inside the method to determine whether the method is static or not.
Furthermore, it’s worth noting that the access flags aren’t identical to modifiers defined in Java. Some access flags and modifiers have a one-to-one mapping, such as public. However, some modifiers, such as sealed, don’t have specified access flags. Likewise, we can’t map some access flags, like synthetic, to the corresponding modifier value.
Going further, since some modifiers share the exact bit representation, we may come to the wrong conclusions if we don’t consider the context in which modifiers are used.
Let’s call the Modifier.toString() on the varArgsMethod():
@Test
void givenVarArgsMethod_whenGetModifiers_thenReturnPublicTransientModifiers() throws Exception {
Class<?> clazz = Class.forName(AccessFlagDemo.class.getName());
Method method = clazz.getMethod("varArgsMethod", String[].class);
int methodModifiers = method.getModifiers();
assertEquals("public transient", Modifier.toString(methodModifiers));
}
The method returns a public transient as a result. Without considering the context, we might conclude that the varArgsMethod() is transient, which isn’t accurate.
On the other hand, access flags consider the context of where the bits come from. Therefore, it provides the correct information:
@Test
void givenVarArgsMethod_whenGetAccessFlag_thenReturnPublicVarArgsFlags() throws Exception {
Class<?> clazz = Class.forName(AccessFlagDemo.class.getName());
Method method = clazz.getMethod("varArgsMethod", String[].class);
Set<AccessFlag> accessFlagSet = method.accessFlags();
assertEquals("[PUBLIC, VARARGS]", accessFlagSet.toString());
}
5. Conclusion
In this article, we learned what the JVM access flags are and how to use them.
To sum up, JVM access flags contain information about access permissions and other properties on the runtime members, such as classes, methods, and fields. We can utilize access flags to get accurate information about modifiers on specific elements.
As always, the entire code examples are available over on GitHub.