Java: JDK 7의 새로운 기능 - Java외 언어 지원

앞 블로그 글에서 살펴본 JDK 7의 새로운 기능 중 Java 외 다른 개발 언어 지원 기능에 대해 좀 더 알아보자.

Java Virtual Machine은 사실 Java 언어와는 아무런 연관이 없다. Java Virtual Machine 스팩 문서Introduction을 보면 아래와 같은 문장이 나온다.

The Java virtual machine knows nothing of the Java programming language, only of a particular binary format, the class file format. A class file contains Java virtual machine instructions (or bytecodes) and a symbol table, as well as other ancillary information.

즉, JVM은 Java 언어에 대해서는 아무것도 모르며 단지, byte 코드와 기타 정보를 가진 class 파일 포맷에 대해서만 알고 있다는 것이다. Java 언어가 아니라도 Java Virtual Machine 스팩 문서에서 정의한 클래스 포맷과 byte 코드 명령어 규칙을 따르기만 하면 JVM에서 실행할 수 있다.
이미 위와 같이 다양한 개발 언어들을 Java Virtual Machine에서 실행할 수 있다. 왜 이처럼 여러 개발 언어들을 JVM에서 실행하게끔 새로 구현하는 것일까? 몇 가지 이유가 있을 수 있다.

Java가 비록 전 세계적으로 가장 많은 개발자를 거느린 훌륭한 개발 언어임은 틀림없지만 RubyPython과 같이 새롭게 떠오르고 있는 동적 언어를 사용하길 원하는 개발자도 있다. 그렇다면, Java의 이점과 동적 스크립트 언어의 장점을 함께 활용할 수 있다면 좋지 않을까?

또한, 언어 개발자로서는 Java Virtual Machine의 고성능과 좋은 기능을 언어 런타임으로 재활용할 수 있는 장점이 있다. 예를 들어, 새로운 스크립트 언어를 개발하는 데 가비지 콜렉션이 필요하다면 새로 구현하는 것보다는 Java Virtual Machine에 기대는 편이 났다. JIT 컴파일러, 다양한 운영체제 지원 등 많은 장점이 있을 것이다. 덤으로 Java 클래스 라이브러리를 이용할 수 있는 장점도 있다.

매우 활발한 모습을 보이고 있는 JRuby의 경우를 보면 Ruby의 모든 기능을 제공하면서 Java의 클래스 라이브러리도 사용할 수 있다. 성능은 기존 Ruby보다 약 2.5배 정도를 자랑한다. Java Virtual Machine을 활용한 덕분이라 하겠다. 더구나 여러 운영체제나 CPU에 별도로 포팅할 필요도 없다!

(Source: Engine Yard Blog Page)

이처럼 JDK 6.0에서도 이미 여러 스크립트 언어를 지원할 수 있다면 굳이 JDK 7.0에서 새로운 개발 언어 지원이라는 기능을 강조하는 이유는 무엇일까? 이점을 이해하기 위해서는 Java Virtual Machine의 깊숙한 내면을 살펴보아야 한다. 자... 심호흡 한번 하고.

기존 Java Virtual Machine에서의 동적 스크립트 언어 지원 문제

여러분이 잘 알고 있듯이 Java는 정적 형 언어이다. 객체든 변수든 사용하기 위해서는 프로그램 코드에 형을 지정해 주어야 한다. 어떤 메쏘드를 호출하기 위해서는 메쏘드를 가진 객체, 메쏘드의 인자 값, 반환 값이 어떤 형태인지 알고 있어야 한다.

이와 달리, Ruby와 같은 동적 형 언어는 메쏘드를 호출하기 위해 객체의 형이 무엇인지 몰라도 상관없다. 런타임이 실행 중에 적절한 메쏘드를 호출하거나 혹은, 런타임 에러를 발생시킨다.

def testMethod(a)
# a가 어떤 형이지? yieldResult라는 메쏘드를 가졌나?
# Ruby 런타임이 실행 시간에 알아서 처리한다.
a.yieldResult()
end

이런 Ruby의 동적인 특성을 Java Virtual Machine에서 자연스럽게 구현할 방법은 없다. 왜 그런지 살펴보자. JVM에서 메쏘드를 호출하기 위해 정의한 byte code 명령어에 4가지가 있다.
  • invokevirtual
  • invokeinterface
  • invokestatic
  • invokespecial
이 중 invokevirtual은 클래스의 메쏘드를 호출하기 위한 것으로 가장 흔하게 사용되는 명령어이다. invokeinterface는 인터페이스 메쏘드를 호출하기 위해 사용한다. 나머지 2개에 대해서는 이 글에서는 굳이 살펴볼 필요가 없으니 건너뛰자.

Ruby를 JVM에 구현할 때 invokevirtual 혹은 invokeinterface 명령어를 이용하여 Ruby 메쏘드를 호출하도록 해야 할 것이다. 문제는 JVM이 이 byte code 명령어들을 제대로 사용하기 위해서는 메쏘드의 클래스, 메쏘드 이름, 선언 형식 등의 정보를 필요로 한다는 것이다. 예를 들어보자.
def max(a, b)
if a.lessThan(b)
b
else
a
end
end
위와 같은 Ruby 코드를 JVM에서 동작하도록 byte code 명령어로 컴파일하는 경우를 생각해보자. a.lessThan(b)를 byte code로 변환하기 위해서는 a의 클래스가 무엇인지 알아야 invokevirtual을 이용하여 lessThan을 호출하도록 컴파일할 수 있는데 Ruby 코드에는 a에 대한 아무런 정보도 없다. a에 대한 형 정보는 컴파일 타임이 아닌 런타임에만 알 수 있다.

단순한 해결책으로 java.lang.reflect.Method 객체를 이용하여 lessThan 메쏘드를 호출하도록 구현할 수 있다. 런타임 시에 reflection을 이용하여 a 객체의 "lessThan"에 대한 Method 객체를 생성하고 invokevirtual은 생성한 Method 객체의 invoke를 호출하여 원하는 결과를 얻는다.

매번 메쏘드를 호출 할 때마다 이런 과정이 이루어져야 한다면 당연히 수행 성능이 떨어질 것이다. 사실 이런 문제를 해결하기 위해 JDK 7.0에서는 새로운 Java Virtual Machine 명령어를 추가한 것이다.

JDK 7.0의 해결책 - invokedynamic

JDK 7.0의 Java Virtual Machine은 새로운 명령어 - invokedynamic - 을 추가하여 동적 스크립트 언어가 Java 언어와 거의 같은 성능을 낼 수 있도록 한다.

invokedynamic 명령어는 앞에서 살펴본 invokevirtual과 달리 메쏘드 호출을 위해 클래스 정보가 필요하지 않다. 호출할 메쏘드의 이름과 선언 형식만 제공하면 런타임에 적절한 메쏘드를 호출할 수 있다.

invokedynamic은 어떻게 런타임에 적절한 메쏘드를 호출할 수 있을까? JDK 7.0은 CallSite, MethodHandle 그리고 bootstrap 메쏘드 등을 이용하여 invokedynamic이 가능케 한다. 동작 원리를 살펴 보자.
  1. 동적 스크립트 언어의 메쏘드 호출에 해당하는 부분을 invokedynamic 명령어로 컴파일한다.
  2. 실행 시 JVM이 invokedynamic 명령어를 만나면 이에 대한 CallSite가 있는지 본다.
  3. 만일 CallSite가 없다면 현재 invokedynamic을 위해 등록된 bootstrap 메쏘드를 호출한다. bootstrap 메쏘드는 동적 언어의 런타임이 제공하는 것이다.
  4. bootstrap 메쏘드는 동적 스크립트 언어의 메쏘드가 호출될 수 있도록 CallSite 객체를 생성하여 반환한다. (CallSite 객체는 실제 호출할 메쏘드에 대한 MethodHandle을 설정할 수 있는 setTarget 그리고 설정된 MethodHandle을 얻어오는 getTarget 메쏘드를 제공한다.)
  5. invokedynamic은 CallSite에 등록된 MethodHandle을 이용하여 메쏘드를 호출한다.
  6. 이미 CallSite가 등록된 invokedynamic이 다시 실행되는 경우 위 과정을 생략하고 바로 메쏘드를 호출할 수 있다.
JDK 7.0은 위와 같은 방법에 의해 기존 JDK 6.0에 비해 동적 스크립트 언어의 수행 성능을 향상한다. 또한, 개발자는 꼼수(?)를 사용하지 않고 동적 스크립트 언어의 메쏘드 호출을 편하게 구현할 수 있다.

하지만, JRuby의 경우에는 이미 위와 유사한 방식의 구현을 따르고 있는 것으로 보여 과연 JDK 7.0의 새로운 방법을 적용할 때 얼마나 성능 향상을 이룰 수 있을지 모르겠다. 이에 대해서는 아래 참고 자료에서 A First Taste Of InvokeDynamic 글을 읽어보아야 겠다. 지금은 졸려서 이만...

UPDATE:

JRuby 1.6을 개발하고 있는 EngineYard의 Thomas Enbo가 SD Times와의 인터뷰에서 아래와 같은 언급을 하였다.

"“We can already do all the things ‘invoke dynamic’ allows in our codebase today, but it means we have to generate lots of Java classes to accomplish the same thing," said Enebo. "We end up loading lots of classes, which slows down load time and eats up method inlining budgets. Invoke dynamic is not supposed to be a part of the inline budget, so in theory, things could end up being dramatically faster. So far, however, they've just been a little faster." "

즉, JDK 7이 제공하는 invokedynamic을 이용하면 JRuby 1.6에서 성능 향상이 많이 기대된다는 것인데 현재는 살짝 성능 향상이 보이는 정도란다.

참고자료:

댓글

이 블로그의 인기 게시물

Wireless: HotSpot 2.0 이란?

Apple M1 Mac Mini에서 이더리움 (Ethereum) 채굴하기

Java: Java for Game? Java가 Game 개발에 어울릴까?