Java Secret: Generated methods
Overview
The Java compiler generates extra methods which appear in stack traces. These can be confusing at first. What are they and why do they exist?The access model in the JVM
The access model in the JVM has not changed significantly since version 1.0. However, Java 5.0 added features such as inner/nested classes and convariant return types which were not in the original design. So how are they supported?The issue with nested classes
Nested classes can access private methods and fields of an outer class. However the JVM does not support this so the compiler generates methods as a workaround.Private Methods
In this example, the inner class calls a private method init() of the outer class.
public class PrivateMethod { // line 4 private void init() { throw new UnsupportedOperationException(); // line 6 }; class Inner { Inner() { init(); } } public static void main(String... args) { PrivateMethod pm = new PrivateMethod(); Inner inner = pm.new Inner(); } }
So what does the stack trace look like when we run this?
Exception in thread "main" java.lang.UnsupportedOperationException at com.google.code.java.core.javac.generated.PrivateMethod.init(PrivateMethod.java:6) at com.google.code.java.core.javac.generated.PrivateMethod.access$000(PrivateMethod.java:4) at com.google.code.java.core.javac.generated.PrivateMethod$Inner.(PrivateMethod.java:11) at com.google.code.java.core.javac.generated.PrivateMethod.main(PrivateMethod.java:17)
In the stack trace there is a method called access$000 on line 4 which is the first line of the class. Is this a real method? What do we see in the byte code?
$ javap -c -private -classpath . com.google.code.java.core.javac.generated.PrivateMethod static void access$000(com.google.code.java.core.javac.generated.PrivateMethod); Code: 0: aload_0 1: invokespecial #1; //Method init:()V 4: return $ javap -c -private -classpath . com.google.code.java.core.javac.generated.PrivateMethod\$Inner Compiled from "PrivateMethod.java" public class com.google.code.java.core.javac.generated.PrivateMethod$Inner extends java.lang.Object{ final com.google.code.java.core.javac.generated.PrivateMethod this$0; com.google.code.java.core.javac.generated.PrivateMethod$Inner(com.google.code.java.core.javac.generated.PrivateMethod); Code: 0: aload_0 1: aload_1 2: putfield #1; //Field this$0:Lcom/google/code/java/core/javac/generated/PrivateMethod; 5: aload_0 6: invokespecial #2; //Method java/lang/Object."The compiler has added a static method which is not private so the Inner class can access the private method of the outer class indirectly.":()V 9: aload_1 10: invokestatic #3; //Method com/google/code/java/core/javac/generated/PrivateMethod. access$000:(Lcom/google/code/java/core/javac/generated/PrivateMeth 13: return }
Private Fields
Private fields are accessed via a getter and setter, but the compiler can generate methods for assignment operations such as ++ and *=public class PrivateField { private int num = 0; public class Inner { public void set(int n) { num = n; } public int get() { return num; } public void increment() { num++; } public void multiply(int n) { num *= n; } } }Compiles with four generated methods, one for each of get, set, ++ and *=
$ javap -c -private -classpath . com.google.code.java.core.javac.generated.PrivateField setter static int access$002(com.google.code.java.core.javac.generated.PrivateField, int); Code: 0: aload_0 1: iload_1 2: dup_x1 3: putfield #1; //Field num:I 6: ireturn getter static int access$000(com.google.code.java.core.javac.generated.PrivateField); Code: 0: aload_0 1: getfield #1; //Field num:I 4: ireturn increment ++ static int access$008(com.google.code.java.core.javac.generated.PrivateField); Code: 0: aload_0 1: dup 2: getfield #1; //Field num:I 5: dup_x1 6: iconst_1 7: iadd 8: putfield #1; //Field num:I 11: ireturn multiplier *= static int access$028(com.google.code.java.core.javac.generated.PrivateField, int); Code: 0: aload_0 1: dup 2: getfield #1; //Field num:I 5: iload_1 6: imul 7: dup_x1 8: putfield #1; //Field num:I 11: ireturn
Covariant return types
Covariant return types is a feature which was added to Java 5.0 but isn't supported in the JVM. For this reason, the compiler generates a method which overrides all the super methods and their return types.public class CovariantReturnTypeA { public Object method() { return 1L; } } public class CovariantReturnTypeB extends CovariantReturnTypeA { public Number method() { return 2.0; } } public class CovariantReturnTypeC extends CovariantReturnTypeB { public Integer method() { return 3; } }
The last method() overrides the method which returns Number and the method which returns Object. To implement this, the compile generates a method with the same signature which calls the method which returns Integer
$ javap -c -private -classpath . com.google.code.java.core.javac.generated.CovariantReturnTypeC public java.lang.Integer method(); Code: 0: iconst_3 1: invokestatic #2; //Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer; 4: areturn public java.lang.Number method(); Code: 0: aload_0 1: invokevirtual #3; //Method method:()Ljava/lang/Integer; 4: areturn public java.lang.Object method(); Code: 0: aload_0 1: invokevirtual #3; //Method method:()Ljava/lang/Integer; 4: areturnThere are three implementations of method() in the byte code.
Performance implications
The performance implications for Java Standard Edition are small as these methods can be inlined and effectively have no impact. In Java Mobile Edition, this might not be the case.They can cause some confusion and if this is a problem you can make the fields/methods package-local instead and the generated methods will go away.
Interestingly, in the private field case, the Eclipse compiler does something different; it adds two generated methods, one to get the value of num, and another to set it. The four methods in the inner class are then implemented in terms of these two generated methods.
ReplyDeleteSo whereas the Oracle compiler effectively moves the code of the four inner class methods into the four generated methods, the Eclipse compiler just generates a getter/setter.
And another interesting Oracle/Eclipse difference: in the covariance example, the Eclipse compiler doesn't add to CovariantReturnTypeC a method that returns Object; instead CovariantReturnTypeC inherits that method from CovariantReturnTypeB.
ReplyDelete