Sunday, September 27, 2009
Deployment Plan Patch Contribution for Clownfish
During my absence, I've spent a little bit of time looking at Android+ZXing early this year and MyFaces Orchestra recently. (Still trying to get my hands dirty with Google App Engine - ported Hibernate Annotations to JPA, still have a long way to go.)
Yesterday, I went back to have a look at my plugin, with much surprise a "Good Samaritan" offered a patch code that would enable Clownfish to deploy through a deployment plan, unit tests included! This is one of the functionality that I left off that I find I hardly use. I'm glad that someone had tried using Clownfish as a Maven2 Glassfish deployer!
Looking forward this coming long weekend to review and hopefully merge this patch back to Clownfish codebase.
Burned by Public Constants
Const.java:
1: public class Const {
2: public static final int VALUE = 600;
3: public void test() {
4: int a = VALUE;
5: System.out.println("a=" + a);
6: }
7: public static void main(String[] args) {
8: new Const().test();
9: }
10: }
ConstClient.java:
1: public class ConstClient {
2: public void test() {
3: int b = Const.VALUE;
4: System.out.println("b=" + b);
5: }
6: public static void main(String[] args) {
7: new ConstClient().test();
8: }
9: }
Compile and run both classes:
Const.java displays:
a=600
ConstClient.java displays:
b=600
So far so good, this is what we expected.
Let's try disassembling them with "javap -c Const"
1: Compiled from "Const.java"
2: public class Const extends java.lang.Object{
3: public static final int VALUE;
4: public Const();
5: Code:
6: 0: aload_0
7: 1: invokespecial #1; //Method java/lang/Object."<init>":()V
8: 4: return
9: public void test();
10: Code:
11: 0: sipush 600
12: 3: istore_1
13: 4: getstatic #2; //Field java/lang/System.out:Ljava/io/PrintStream;
14: 7: new #3; //class java/lang/StringBuilder
15: 10: dup
16: 11: invokespecial #4; //Method java/lang/StringBuilder."<init>":()V
17: 14: ldc #5; //String a=
18: 16: invokevirtual #6; //Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
19: 19: iload_1
20: 20: invokevirtual #7; //Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder;
21: 23: invokevirtual #8; //Method java/lang/StringBuilder.toString:()Ljava/lang/String;
22: 26: invokevirtual #9; //Method java/io/PrintStream.println:(Ljava/lang/String;)V
23: 29: return
24: public static void main(java.lang.String[]);
25: Code:
26: 0: new #10; //class Const
27: 3: dup
28: 4: invokespecial #11; //Method "<init>":()V
29: 7: invokevirtual #12; //Method test:()V
30: 10: return
31: }
and "javap -c ConstClient"
1: Compiled from "ConstClient.java"
2: public class ConstClient extends java.lang.Object{
3: public ConstClient();
4: Code:
5: 0: aload_0
6: 1: invokespecial #1; //Method java/lang/Object."<init>":()V
7: 4: return
8: public void test();
9: Code:
10: 0: sipush 600
11: 3: istore_1
12: 4: getstatic #2; //Field java/lang/System.out:Ljava/io/PrintStream;
13: 7: new #3; //class java/lang/StringBuilder
14: 10: dup
15: 11: invokespecial #4; //Method java/lang/StringBuilder."<init>":()V
16: 14: ldc #5; //String b=
17: 16: invokevirtual #6; //Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
18: 19: iload_1
19: 20: invokevirtual #7; //Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder;
20: 23: invokevirtual #8; //Method java/lang/StringBuilder.toString:()Ljava/lang/String;
21: 26: invokevirtual #9; //Method java/io/PrintStream.println:(Ljava/lang/String;)V
22: 29: return
23: public static void main(java.lang.String[]);
24: Code:
25: 0: new #10; //class ConstClient
26: 3: dup
27: 4: invokespecial #11; //Method "<init>":()V
28: 7: invokevirtual #12; //Method test:()V
29: 10: return
30: }
We see that from Const.class line 11 and ConstClient.class line 10, the constant value 600 is pushed onto the stack memory at slot #1.
Let's try changing Const.java constant value from 600 to 500.
1: public class Const {
2: public static final int VALUE = 500;
3: public void test() {
4: int a = VALUE;
5: System.out.println("a=" + a);
6: }
7: public static void main(String[] args) {
8: new Const().test();
9: }
10: }
Compile Const.java and run produces:
a=500
Rerun ConstClient without recompilation produces strange result:
b=600
Let's take a peek by disassembling Const.class:
1: Compiled from "Const.java"
2: public class Const extends java.lang.Object{
3: public static final int VALUE;
4: public Const();
5: Code:
6: 0: aload_0
7: 1: invokespecial #1; //Method java/lang/Object."<init>":()V
8: 4: return
9: public void test();
10: Code:
11: 0: sipush 500
12: 3: istore_1
13: 4: getstatic #2; //Field java/lang/System.out:Ljava/io/PrintStream;
14: 7: new #3; //class java/lang/StringBuilder
15: 10: dup
16: 11: invokespecial #4; //Method java/lang/StringBuilder."<init>":()V
17: 14: ldc #5; //String a=
18: 16: invokevirtual #6; //Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
19: 19: iload_1
20: 20: invokevirtual #7; //Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder;
21: 23: invokevirtual #8; //Method java/lang/StringBuilder.toString:()Ljava/lang/String;
22: 26: invokevirtual #9; //Method java/io/PrintStream.println:(Ljava/lang/String;)V
23: 29: return
24: public static void main(java.lang.String[]);
25: Code:
26: 0: new #10; //class Const
27: 3: dup
28: 4: invokespecial #11; //Method "<init>":()V
29: 7: invokevirtual #12; //Method test:()V
30: 10: return
31: }
We see that line 11 pushes our new constant value 500 onto the stack.
Let's take another peek at ConstClient.class:
1: Compiled from "ConstClient.java"
2: public class ConstClient extends java.lang.Object{
3: public ConstClient();
4: Code:
5: 0: aload_0
6: 1: invokespecial #1; //Method java/lang/Object."<init>":()V
7: 4: return
8: public void test();
9: Code:
10: 0: sipush 600
11: 3: istore_1
12: 4: getstatic #2; //Field java/lang/System.out:Ljava/io/PrintStream;
13: 7: new #3; //class java/lang/StringBuilder
14: 10: dup
15: 11: invokespecial #4; //Method java/lang/StringBuilder."<init>":()V
16: 14: ldc #5; //String b=
17: 16: invokevirtual #6; //Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
18: 19: iload_1
19: 20: invokevirtual #7; //Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder;
20: 23: invokevirtual #8; //Method java/lang/StringBuilder.toString:()Ljava/lang/String;
21: 26: invokevirtual #9; //Method java/io/PrintStream.println:(Ljava/lang/String;)V
22: 29: return
23: public static void main(java.lang.String[]);
24: Code:
25: 0: new #10; //class ConstClient
26: 3: dup
27: 4: invokespecial #11; //Method "<init>":()V
28: 7: invokevirtual #12; //Method test:()V
29: 10: return
30: }
Line 10 tells us that for some reason our program pushes 600 onto the stack not 500 - our constant value is still 600! It seems like javac burned the constant value into ConstClient.class instead of JVM resolving it through Const.class!
- "Extra care should be taken when hot-deploying classes with public constants as they could potentially break your code during runtime."
Saturday, September 26, 2009
The Curious Case of Enums and Autoboxing
switch (expression) {
...
}
However as of JDK 5, Sun introduced enumeration type and auto-boxing and because of this, the switch statement also supports these two concepts.
Below is sample code for JDK 5 and later:
1: public class CaseEnum {
2: enum ResultEnum { OK, CANCEL };
3: public void test1() {
4: ResultEnum result = ResultEnum.OK;
5: switch (result) {
6: case OK:
7: case CANCEL:
8: }
9: }
10: public void test2() {
11: switch (new Integer(2)) {
12: case 1:
13: case 2:
14: }
15: }
16: public void test3() {
17: switch (new Byte((byte) 3)) {
18: case 1:
19: case 2:
20: case 3:
21: }
22: }
23: }
Compiling with JDK6 and disassembling this class reveals:
1: Compiled from "CaseEnum.java"
2: public class CaseEnum extends java.lang.Object{
3: public CaseEnum();
4: Code:
5: 0: aload_0
6: 1: invokespecial #1; //Method java/lang/Object."<init>":()V
7: 4: return
8: public void test1();
9: Code:
10: 0: getstatic #2; //Field CaseEnum$ResultEnum.OK:LCaseEnum$ResultEnum;
11: 3: astore_1
12: 4: getstatic #3; //Field CaseEnum$1.$SwitchMap$CaseEnum$ResultEnum:[I
13: 7: aload_1
14: 8: invokevirtual #4; //Method CaseEnum$ResultEnum.ordinal:()I
15: 11: iaload
16: 12: lookupswitch{ //2
17: 1: 40;
18: 2: 40;
19: default: 40 }
20: 40: return
21: public void test2();
22: Code:
23: 0: new #5; //class java/lang/Integer
24: 3: dup
25: 4: iconst_2
26: 5: invokespecial #6; //Method java/lang/Integer."<init>":(I)V
27: 8: invokevirtual #7; //Method java/lang/Integer.intValue:()I
28: 11: lookupswitch{ //2
29: 1: 36;
30: 2: 36;
31: default: 36 }
32: 36: return
33: public void test3();
34: Code:
35: 0: new #8; //class java/lang/Byte
36: 3: dup
37: 4: iconst_3
38: 5: invokespecial #9; //Method java/lang/Byte."<init>":(B)V
39: 8: invokevirtual #10; //Method java/lang/Byte.byteValue:()B
40: 11: tableswitch{ //1 to 3
41: 1: 36;
42: 2: 36;
43: 3: 36;
44: default: 36 }
45: 36: return
46: }
From line 14, javac adds an extra command to turn the enum into an integer value by invoking the enum "ordinal" method: CaseEnum$ResultEnum.ordinal:()I
From line 27, javac adds an extra command to unbox Integer to int by invoking the "intValue" method:
java/lang/Integer.intValue:()I
From line 39, again javac adds another extra command to unbox Byte to byte by invoking the "byteValue" method:
java/lang/Byte.byteValue:()B
Java String, StringBuilder and StringBuffer
The urge to scratch this itch finally made me fire up “vi” from my terminal. Yes I know what you are thinking, I'm no "vi" expert, but do I really need Eclipse to write 15 lines of code? :)
Here is the source:
1: public class Str {
2: public void sayHelloWorld() {
3: String str1 = "hello " + "world ";
4: String str2 = "hello world again";
5: }
6: public void sayGoodbyeWorld() {
7: String str3 = "goodbye world " + 3;
8: }
9: public void sayYouSayMe() {
10: String str4 = "";
11: for (int i = 0; i < 3; i++) {
12: str4 += "say you say me ";
13: }
14: }
15: }
Compiling the source with JDK 6 followed by
“javap -c Str" gives us:
1: Compiled from "Str.java"
2: public class Str extends java.lang.Object{
3: public Str();
4: Code:
5: 0: aload_0
6: 1: invokespecial #1; //Method java/lang/Object."<init>":()V
7: 4: return
8: public void sayHelloWorld();
9: Code:
10: 0: ldc #2; //String hello world
11: 2: astore_1
12: 3: ldc #3; //String hello world again
13: 5: astore_2
14: 6: return
15: public void sayGoodbyeWorld();
16: Code:
17: 0: ldc #4; //String goodbye world 3
18: 2: astore_1
19: 3: return
20: public void sayYouSayMe();
21: Code:
22: 0: ldc #5; //String
23: 2: astore_1
24: 3: iconst_0
25: 4: istore_2
26: 5: iload_2
27: 6: iconst_3
28: 7: if_icmpge 36
29: 10: new #6; //class java/lang/StringBuilder
30: 13: dup
31: 14: invokespecial #7; //Method java/lang/StringBuilder."<init>":()V
32: 17: aload_1
33: 18: invokevirtual #8; //Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
34: 21: ldc #9; //String say you say me
35: 23: invokevirtual #8; //Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
36: 26: invokevirtual #10; //Method java/lang/StringBuilder.toString:()Ljava/lang/String;
37: 29: astore_1
38: 30: iinc 2, 1
39: 33: goto 5
40: 36: return
41: }
The disassembled JDK 6 code from above reveals quite a bit about this simple class.
Line 2: the compiler automatically extends our class from java.lang.Object.
Line 3-7: the compiler automatically inserts a default Str constructor that invokes java.lang.Object constructor
Line 10: ldc #2 suggests that “hello world” string is stored as the 2nd item from constant pool table, where “hello " and "world” is automagically concatenated for you by the compiler
Line 12: ldc #3 suggests that “hello world again” string is stored as the 3rd item from constant pool table
Line 17: ldc #4 suggests that “goodbye world 3” is stored as the 4th item from constant pool table, where the compiler again automagically concatenated integer “3” to "goodbye world "
Line 22: ldc #5 suggests that our empty string is stored as the 5th item from constant pool table
Line 29-36: tells us that javac inserts StringBuilder to concatenate "say you say me " strings with "say you say me " stored as the 9th item from constant pool table
Line 36: StringBuilder object invokes the toString method and assigns it back to “str4”
Let's try compiling the same source with JDK 1.4 by running “javac -source 1.4 -target 1.4 Str.java” then followed by “javap -c Str” which gives us:
1: Compiled from "Str.java"
2: public class Str extends java.lang.Object{
3: public Str();
4: Code:
5: 0: aload_0
6: 1: invokespecial #1; //Method java/lang/Object."<init>":()V
7: 4: return
8: public void sayHelloWorld();
9: Code:
10: 0: ldc #2; //String hello world
11: 2: astore_1
12: 3: ldc #3; //String hello world again
13: 5: astore_2
14: 6: return
15: public void sayGoodbyeWorld();
16: Code:
17: 0: ldc #4; //String goodbye world 3
18: 2: astore_1
19: 3: return
20: public void sayYouSayMe();
21: Code:
22: 0: ldc #5; //String
23: 2: astore_1
24: 3: iconst_0
25: 4: istore_2
26: 5: iload_2
27: 6: iconst_3
28: 7: if_icmpge 36
29: 10: new #6; //class java/lang/StringBuffer
30: 13: dup
31: 14: invokespecial #7; //Method java/lang/StringBuffer."<init>":()V
32: 17: aload_1
33: 18: invokevirtual #8; //Method java/lang/StringBuffer.append:(Ljava/lang/String;)Ljava/lang/StringBuffer;
34: 21: ldc #9; //String say you say me
35: 23: invokevirtual #8; //Method java/lang/StringBuffer.append:(Ljava/lang/String;)Ljava/lang/StringBuffer;
36: 26: invokevirtual #10; //Method java/lang/StringBuffer.toString:()Ljava/lang/String;
37: 29: astore_1
38: 30: iinc 2, 1
39: 33: goto 5
40: 36: return
41: }
The disassembled code for 1.4 and 1.6 looks the same except that 1.4 uses StringBuffer instead of StringBuilder as StringBuilder was introduced as part of JDK 5 and later. So it looks like a little bit of String optimisation is done on the Javac level not on the JVM level.