기본 콘텐츠로 건너뛰기

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 이란?

스마트폰 사용자가 HotSpot 2.0을 지원하는 Wi-Fi 망을 사용하는 경우라면 기존 Wi-Fi 망과 달리 이동통신 망에서 Wi-Fi 망으로의 네트워크 연결 전환이 자연스럽게 이루어진다. 예를 들면, 3G 네트워크를 이용하여 영화를 보고 있다가 HotSpot 2.0 네트워크에 연결이 가능하게 되면 영화 시청 중단 없이 Wi-Fi 망으로 자연스럽게 네트워크 연결이 이동하여 3G 망의 부하도 줄이고 사용자의 네트워크 비용도 절약할 수 있다. 시스코에서 제공한 White Paper 를 참고.

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

 돈을 벌 목적은 아니고 이더리움 기술에 대한 호기심에 직접 채굴(마이닝)에 나서 보기로 했다. 머신은 Apple M1 Mac Mini. 스팩을 살펴보니 8 Core GPU에 16GB 메모리를 공유하고 있어 가능은 해보인다. 큰 흐름은 다음과 같다. 채굴한 이더리움을 저장할 지갑을 만든다 만든 지갑의 정보를 잘 보관해둔다 (Secret Recovery Phrase, 지갑의 주소 값) Apple M1용 채굴 프로그램 설치 내 지갑 정보를 이용해서 채굴 프로그램 실행 일단, 채굴한 이더리움을 저장할 지갑(wallet)을 만들어야 한다.  크롬 브라우저 익스텐션 설치로 비교적 간단하게 지갑을 만들 수 있는  https://metamask.io/ 를 이용하기로 했다. 크롬 익스텐션을 설치 후 기존에 만든 지갑이 없으므로 "Create a Wallet"을 선택한다. 패스워드 입력하고 등등의 절차를 거치면 아래와 같은 Secret Recovery Phrase가 나온다. 이 값을 잘 보관해두기 바란다. 나중에 지갑을 복구할 때 필요한 값이다. 이 값이 유출되면 지갑에 모아둔 이더리움을 다 털릴 수 있으므로 안전한 곳에 보관한다. Confirm Your Secret Phrase에서 확인 과정을 거친다. 직접 입력하는 것이 아니라 단어 별 버튼을 일일이 클릭해서 확인해주어야 한다. (좀 번거롭지만 그만큼 Secret Recovery Phrase가 중요함을 인지시키기 위한 과정이다.) 이제 지갑은 준비 완료. 생성된 Account 화면에서 지갑의 주소갑을 얻을 수 있다.  Apple M1용 채굴 프로그램을 설치해보자. Ethminer M1 Github 프로젝트 에서 미리 컴파일된 바이너리를 다운로드 받는다. (Assets를 펼치고 ethminer-m1을 클릭해서 다운 받으면 된다) 원하는 폴더에 파일을 옮겨 놓고 Terminal에서 chmod +x로 실행가능하게 만든다. % mv ~/Downloads/ethminer-m1 .             %   % c

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

Java가 기업용 서버 소프트웨어 개발에 활발하게 쓰이는 것과 달리 일반 응용프로그램 분야에서는 별로 대접을 받지 못하는 현실을 개선하려면 어떤 분야부터 손보면 좋을까? 로딩타임, 성능, 사용자 인터페이스 등 Java를 이용한 클라이언트 프로그램을 개발하지 않는 이유들은 개발자 별로 서로 다를 것이다. 하지만, 이런 단점에도 불구하고 점점 복잡해지는 소프트웨어를 더 쉽게 다양한 환경에서 동작하도록 만들기 위해서는 Java만큼 이미 성숙한 해결책도 없지 않은가? 클라이언트 개발을 활성화하기 위해 Java를 게임 개발에 활용할 수 있도록 지원하면 어떨까? 역시, 사용자가 직접 쓰는 응용프로그램 중에는 게임이 가장 시장이 큰 분야이니 말이다. 그렇다면, 현재 게임 개발에 Java가 어느 정도 사용되고 있고 미래에 더 활성화 될 가능성은 있을 것인가? 이런 의문점을 가지고 "Java + Game"에 대해 조사해보기로 했다. 1. Java로 개발한 게임들 우선, Java로 개발한 게임들에 대해 살펴보자. Oracle의  Java in Action 웹페이지 를 보면 Java를 이용한 3D MMORPG RuneScape 에 대한 설명이 있다. 가입자가 1억 3000만명이 넘는다고 하다. 실제 게임을 설치해서 실행해보니 WOW같은 화려한 그래픽에는 못미치치만 잘만든 Role Playing 게임이다 Puppy Games 에서 개발한  Revenge of the Titans 게임도 눈여겨 볼 만하다. 이 회사는 작고 손쉽게 즐길 수 있는 게임을 개발하는데 모든 게임을 Java로 만든다. Revenge of the Titans는 Starcraft와 같은 전략 게임으로 너무 머리쓰지 않고 즐길 수 있는 게임이며 그래픽도 신선하다. 개발이 진행 중인 것으로 보이는 Urban Galaxy 라는 게임도 재미있을 것 같다. SF 영화를 보면 자주 등장하는 미래의 빌딩 숲을 날아다니는 자동차로 전투도 치르고 무역도 하며 캐릭터를 키우는 게임으로