자바 클래스가 어떻게 되어 있는지 분석하려면, asm library를 써서 확인할 수 있다.
이외, 간단하게 콘솔로 출력할 수 있는 툴이 있어서 소개한다.

jcf-dump 라는 툴인데, gcc java를 설치하면 gcj와 함께 있는 툴이다.

http://linux.die.net/man/1/jcf-dump

Name

jcf-dump - print information about Java class files

Synopsis

jcf-dump [-c] [--javap] [--classpath=path] [--CLASSPATH=path] [-Idir...] [-o file] [--version] [--help] [-v] [--verbose] classname...


.............



gcj를 설치한다.
$ sudo apt-get install gcj-jdk

간단하게 클래스 파일을 만들어본다.

$ vi test.java
public class test {
    public static void main(String[] args) {
        int a = 0;
        String temp = "temp";
        a++;
        System.out.println("a:" + a);
    }
}



jcf-dump 를 이용하면, magic number 넘버가 나오고 compile(target)된 버전이 나온다. major_version이 50이면, jdk 1.6이라는 뜻이다.  그리고, Constant pool  정보, Fields, Methods 정보가 바로 나온다.

$ jcf-dump test
Reading .class from ./test.class.
Magic number: 0xcafebabe, minor_version: 0, major_version: 50.
Constant pool (count: 45):
#1: Methodref class: 12=java.lang.Object name_and_type: 21=<<init> ()void>
#2: String 22="temp"
#3: Fieldref class: 23=java.lang.System name_and_type: 24=<out java.io.PrintStream>
#4: Class name: 25="java/lang/StringBuilder"
#5: Methodref class: 4=java.lang.StringBuilder name_and_type: 21=<<init> ()void>
#6: String 26="a:"
#7: Methodref class: 4=java.lang.StringBuilder name_and_type: 27=<append (java.lang.String)java.lang.StringBuilder>
#8: Methodref class: 4=java.lang.StringBuilder name_and_type: 28=<append (int)java.lang.StringBuilder>
#9: Methodref class: 4=java.lang.StringBuilder name_and_type: 29=<toString ()java.lang.String>
#10: Methodref class: 30=java.io.PrintStream name_and_type: 31=<println (java.lang.String)void>
#11: Class name: 32="test"
#12: Class name: 33="java/lang/Object"
#13: Utf8: "<init>"
#14: Utf8: "()V"
#15: Utf8: "Code"
#16: Utf8: "LineNumberTable"
#17: Utf8: "main"
#18: Utf8: "([Ljava/lang/String;)V"
#19: Utf8: "SourceFile"
#20: Utf8: "test.java"
#21: NameAndType name: 13=<init>, signature: 14=()void
#22: Utf8: "temp"
#23: Class name: 34="java/lang/System"
#24: NameAndType name: 35=out, signature: 36=java.io.PrintStream
#25: Utf8: "java/lang/StringBuilder"
#26: Utf8: "a:"
#27: NameAndType name: 37=append, signature: 38=(java.lang.String)java.lang.StringBuilder
#28: NameAndType name: 37=append, signature: 39=(int)java.lang.StringBuilder
#29: NameAndType name: 40=toString, signature: 41=()java.lang.String
#30: Class name: 42="java/io/PrintStream"
#31: NameAndType name: 43=println, signature: 44=(java.lang.String)void
#32: Utf8: "test"
#33: Utf8: "java/lang/Object"
#34: Utf8: "java/lang/System"
#35: Utf8: "out"
#36: Utf8: "Ljava/io/PrintStream;"
#37: Utf8: "append"
#38: Utf8: "(Ljava/lang/String;)Ljava/lang/StringBuilder;"
#39: Utf8: "(I)Ljava/lang/StringBuilder;"
#40: Utf8: "toString"
#41: Utf8: "()Ljava/lang/String;"
#42: Utf8: "java/io/PrintStream"
#43: Utf8: "println"
#44: Utf8: "(Ljava/lang/String;)V"
Access flags: 0x21 public super
This class: 11=test, super: 12=java.lang.Object
Interfaces (count: 0):
Fields (count: 0):
Methods (count: 2):
Method name:"<init>" public Signature: 14=()void
Attribute "Code", length:29, max_stack:1, max_locals:1, code_length:5
Attribute "LineNumberTable", length:6, count: 1
Method name:"main" public static Signature: 18=(java.lang.String[])void
Attribute "Code", length:74, max_stack:3, max_locals:3, code_length:34
Attribute "LineNumberTable", length:22, count: 5
Attributes (count: 1):
Attribute "SourceFile", length:2, #20="test.java"



만약 1.5로 컴파일하면, major_version은 49가 될 것이다.


아주 큰 장점은..  자바의 타겟 컴파일 이슈가 문제가 될 때, 바로 이 툴로 확인할 수 있을 것이다.

ClassFormatError, ClassNotFoundException 이런 이슈등에 해결될 수 있을 것이다.

  • 46 = Java 1.2
  • 47 = Java 1.3
  • 48 = Java 1.4
  • 49 = Java 5
  • 50 = Java 6
  • 51 = Java 7

-c 옵션을 주어서, method의 실제 동작되는 jvm assembly를 볼 수 있다.
$ gcj-dump -c test.java
Methods (count: 2):
Method name:"<init>" public Signature: 14=()void
Attribute "Code", length:29, max_stack:1, max_locals:1, code_length:5
  0: aload_0
  1: invokespecial #1=<Method java.lang.Object.<init> ()void>
  4: return
Attribute "LineNumberTable", length:6, count: 1
  line: 1 at pc: 0
Method name:"main" public static Signature: 18=(java.lang.String[])void
Attribute "Code", length:74, max_stack:3, max_locals:3, code_length:34
  0: iconst_0
  1: istore_1
  2: ldc #2=<String "temp">
  4: astore_2
  5: iinc 1 1
  8: getstatic #3=<Field java.lang.System.out java.io.PrintStream>
 11: new #4=<Class java.lang.StringBuilder>
 14: dup
 15: invokespecial #5=<Method java.lang.StringBuilder.<init> ()void>
 18: ldc #6=<String "a:">
 20: invokevirtual #7=<Method java.lang.StringBuilder.append (java.lang.String)java.lang.StringBuilder>
 23: iload_1
 24: invokevirtual #8=<Method java.lang.StringBuilder.append (int)java.lang.StringBuilder>
 27: invokevirtual #9=<Method java.lang.StringBuilder.toString ()java.lang.String>
 30: invokevirtual #10=<Method java.io.PrintStream.println (java.lang.String)void>
 33: return
Attribute "LineNumberTable", length:22, count: 5
  line: 3 at pc: 0
  line: 4 at pc: 2
  line: 5 at pc: 5
  line: 6 at pc: 8
  line: 7 at pc: 33


이것을 이용하면, 나중에 java 7에서 invoke 개선안에 대해서 어떻게 동작하는지 쉽게 볼 수 있을 것이다.


javap 결과도 보여줄 수 있다.
$ jcf-dump -javap test
Reading .class from ./test.class.
Magic number: 0xcafebabe, minor_version: 0, major_version: 50.
Access flags: 0x21 public super
This class: test, super: java.lang.Object
Interfaces (count: 0):
Fields (count: 0):
Methods (count: 2):
     public void "<init>"()
     public static void "main"(java.lang.String[])






개발하다보면, java5에서 java6로 넘어가는 과정에서 많은 고민이 있었다. 내년에도 이런 식으로 이슈가 있을 것이다. 개발환경은 java7인데, 운영환경은 java6로 했을때, jvm이 인식못하는 jvm assembly 나 타겟 이슈가 있을 때, 이 툴을 이용하면 개발자들이 자주 실수 하는 부분을 찾아낼 수 있을 것이다.

간단하게 하면 머 이런식이 될 것이다.

$ jcf-dump -javap test | grep 'major_' | awk '{if($7=="50.") print "jdk 1.6"; else if($7=="51.") print "jdk 1.7";}'
jdk 1.6





사실 굳이 jcf-dump로 확인할 필요가 없다. 리눅스의 file 명령어로 확인할 수 있다. ㅎㅎㅎ

$ file test.class
test.class: compiled Java class data, version 50.0


Posted by '김용환'
,