오늘은 java 바이트코드에 대해서 잠깐 살펴보자.
우리는 거의 바이트코드를 볼일이 없다. 바이트코드 레벨까지 내려갈 필요가 없기에 딱히 볼일이 없다. 바이트코드를 상세하게 볼건 없고 그냥 한번 보고 넘어가자.
package me.wonwoo;
public class ApplicationTest {
public static void main(String[] args) {
String str = "wonwoo";
System.out.println(str);
}
}
이런 클래스가 있다고 가정할 때 바이트 코드는 어떻게 나오는지 보자. 인텔리j에서는 컨트롤+쉬프트+a를 눌러 show bytecode 맥 경우에는 커멘드 + 쉬프트 + a 에서 show bytecode를 누르면 된다. 참고로 컴파일을 해야 보인다. 아마도 이클립스에서는 플러그인을 깔아야 될 듯 싶다. 혹은 javap 를 이용해도 된다.
위의 코드를 바이트 코드로 변환 시켰을때는 아래내용과 같다.
복사가 안되서 캡쳐를 했다. 사람이 보기에도 대충은 알것도 같다. 보면 기본 생성자를 만들지 않았지만 기본으로 컴파일러가 만들어준다. lineNumber도 보이고 wonwoo라는 문자열도 보인다. println도 java/lang/System에서 static 변수인 out를 가져온다. 그리고나서 ASTORE 1 에 할당된 wonwoo라는 문자열을 ALOAD로 이용해 ASTORE의 1을 가져오는 느낌? 그 후에 java/io/PrintStream에 println을 호출하여 출력해주는 것 같다. 이건 대충 필자가 끼워맞춘거라.. 자세한 내용은 .. 잘...
대부분 앞에 있는 ALOAD, INVOKESPECIAL, GETSTATIC, INVOKEVIRTUAL 등은 JVM명령어라고 하니 관심있는 사람들은 인터넷에서...
보면 가슴만 아프니 다른 재밌는걸? 해보자.
package me.wonwoo;
public class ApplicationTest {
public static void main(String[] args) {
String str = "wonwoo" + " hello" + " world";
}
}
위와 같은 코드가 있다고 가정하자. 그럼 컴파일러는 어떤 행동을 하는지 보자.
똑똑하게 컴파일러는 위와 같이 한문장으로 해석한다. 그러면 중간에 변수가 끼는 상황일때는 어떻게 되는지 한번 살펴보자.
package me.wonwoo;
public class ApplicationTest {
public static void main(String[] args) {
String s = "java byte code";
String str = "wonwoo" + " hello" + s + " world";
}
}
조금 길어서 main만 캡쳐했다.
가만보면 오호.. StringBuilder를 이용해서 append를 한다. 멍청할줄만 알았던 컴파일러가 똑똑하다. 똑똑한 건가?흠흠
그럼 우리가 흔히 String에서 쓰지말자던 (+)를 살펴보자. 위의 경우에는 한 변수에 계속 할당하는것이 아니라 저렇게 써도 무관할 듯 싶다. 예를 들어 아래와 같은 경우다. 계속 한 변수에 어사인을 하는 그런 코드이다. 다들 아시다시피 이건 추천하지 않는 그런 코드이다. 매번 String 생성하는 그런 코드라 많이 알려져 있다. 실제로도 그런지 보자.
public static void main(String[] args) {
String a = "java";
a += "byte code";
a += "wonwoo";
a += "hello";
a += "world";
}
위와 같은 코드의 바이트 코드는 아래와 같다. 생각보다 훨씬 길어서 짤랐다.
위의 바이트 코드를 보면 StringBuilder를 계속 생성한다. NEW java/lang/StringBuiler 이런식으로 StringBuilder를 계속 생성하고 있다. ASTORE 1에 넣어둔건 합쳐서 다시 1에 넣고 다시 1을 꺼내 또 합치는 것을 반복한다. 우리가 흔히 알고 있던 매번 String을 생성하고 있다. 물론 요즘 PC가 좋아서 짧은 코드야 금방 끝나겠지만 그래도 매번 생성하는 것은 불필요하다. 또한 만약 정말로 많은 작업을 한다면 생각보다 느려질 것이다. 그러니 습관적으로 StringBuilder나 StringBuffer를 쓰자. 둘의 차이는 스레드 세이프 하냐 하지 않느냐다. (참고로 StringBuffer가 스레드 세이프하다. //A thread-safe, mutable sequence of characters. StringBuffer 상단의 주석) 아무튼 그게 중요한게 아니라 concat이나 (+=) 등으로 합치지 말고 StringBuilder를 쓰는걸 권장한다.
bytecode를 공부할 필요는 없다. 그냥 있다는 것만 알아두면 좋겠다. 만약 쓸일이 있다면 아래 소개하는 라이브러리를 참고하면 되겠다.
Bytecode를 컨트롤 할 수 있는 라이브러리도 많다. (이건 나중에 한번 해보겠다.) 유명한 라이브러리에는 ASM, apache BCEL, javassist 등이 있다. 한두가지 더 있는것 같은데..그닥 처음 본거라..
아무튼 ASM이 가장 유명하지 않나 싶다. 흔히 알고 있는 cglib와 lombok이 해당 라이브러리를 쓴다. (lombok은 정확하지 않다.) cglib는 Spring에서 메인이 되는(?) AOP 라이브러리이다. 그래서 더 유명하지 않나 싶다. apache BCEL 같은 경우에는 aspectj가 사용한다고 한다. 그러나 좋지 않은 이유가 BCEL은 업데이트가 엄청 느리다. 예로 2006년에 5.2가 발표 되고 요근래에 새로운 버전인 6.0이 발표되었다. 지금도 있는지 모르겠지만 몇년전에는 ASM을 사용하라고 권장하는 내용도 있었다고 한다. 마지막으로 내기억이 맞다면 javassist는 하이버네이트가 쓰고 있다.(이거 또한 정확한 정보가 아니다. 왜냐하면 spring boot 실행시 하이버네이트에서 javassist 관련 로그가 찍혀서..그런가 싶었다.)
이것으로 java의 바이트 코드를 살펴봤다. 나중에는 ASM을 어떻게 사용하지는 살펴보겠다. (필자도 공부를 해야되서..) 예전에 한번 해보긴 했는데 무슨 코드인지 영 감도 안잡히고 그래서 그냥 그러려니 했다.