참조: http://www.zeroturnaround.com/blog/reloading_java_classes_401_hotswap_jrebel/

HotSwap과 Instrumentation

자바 1.4에 새로 추가된 기술로 Debugger API랑 같이 활용되어 클래스 식별자를 유지하면서 바이트코드를 갱신할 수 있게됐다. 즉 모든 객체가 수정된 클래스를 참조하게 되고 메서드를 호출하면 새 코드가 실행된다. 클래스 바이트코드를 변경해도 컨테이너를 다시 로딩할 필요가 없어졌다. 대부분의 IDE가 이 기능을 지원했고 자바5부터는 Instrumentation API로 자바 애플리케이션에서 이 기능을 사용할 수 있게 됐다.

hotswap

하지만 재정의할 수 있는 건 메서드 몸체로만 제한되었다. 메서드 몸체를 변경하는게 아니라, 새로운 메서드를 추가하거나 새로운 필드를 추가하는 등의 작업을 포함해 아무것도 할 수 없었다. 이런 제약과 더블어 HotSwap에는 몇가지 문제를 가지고 있다.

  • 메서드 몸체만 변경했을 뿐인데 자바 컴파일러가 임의 메서드나 필드를 만들어 낸다.
  • 디버그 모드로 실행하면 애플리케이션이 느려지고 다른 문제를 야기한다.

그래서인지 HotSwap은 예상보다 덜 사용되고 있다.

왜 HowSwap을 메서드 몸체로만 제한하는가?

핫스왑을 클래스 단위로 올려달라는 요청을 했지만 구현되고 있지 않다.

JVM에 객체가 로딩되면 메모리에 자리잡게 되는데, 이때 새 필드를 추가하면 그 구조를 바꿔야 한다. 이때 근처에 영역이 이미 다른 객체가 자리를 잡고 있다면 충분한 공간이 있는 곳으로 다시 배치해야 한다. 그리고 객체가 아니라 클래스를 변경하는 것이기 때문에 해당 클래스의 모든 객체를 다 그렇게 바꿔줘야 한다.

이건 GC가 객체를 재배치 해줄테니 별 문제가 아닌데 “heap”에 대한 추상화가 문제다. 메모리의 실제 레이아웃은 현재 활동중인 GC에 따라 달라진다. 따라서 그런 객체 메모리 재배치 작업을 GC에 위임해야 하며 재채비할 동안 JVM을 잠시 중단시킬 필요가 있다.

새 메서드를 추가한다고 해서 객체 구조를 바꿔야 하는건 아니지만 힙 영역에 자리잡고 있는 클래스 구조는 바꿔야 한다. 그리고 클래스는 일단 로딩되면 고정된다. 그리고 JIT가 최적화 하느라 메서드 호출 부분을 모두 실제 코드로 다 바꿔준다.

바로 이게 골때리는 부분인데 메서드를 추가하면 해당 메서드와 동일한 이름을 가진 메서드가 존재하진 않는진 확인해봐야 하고 타겟 클래스 뿐 아니라 그 상위 클래스도 전부 확인해야 한다. 다른 방법도 있지만 어떤 방법을 쓰던 성능과 복잡도에서 비용이 발생한다.

JRebel

2007년 ZeroTrunaround는 JRebel이란느 툴을 공개했다. 동적 클래스로더를 사용하지 않고 거의 아무런 제약없이 클래스를 갱신할 수 있는 기술이다. HotSwap과는 달리 실제로 컴파일된 .class 파일을 감지하다가 파일이 갱신되면 클래스를 갱신한다.

jrebel-agent

동작 원리

JRebel은 HotSwap처럼 JVM 수준이 아니라 바이크코드와 클래스로더 추상계층을 사용한다. JRebel은 클래스로더를 사용해서 언제 클래스가 로딩되는지 알 수 있고, 그 때 바이트코드를 조작하여 VM과 실행가능한 코드 사이에 추상화 계층을 만든다.

다른 이들은 이 기능을 사용해서 프로파일러, 성능 모니터링, Continuation, Software transactional meemoy, distributed heap등을 가능케 했다.

[자바 클래스 릴로딩 101]에서 봤듯이 클래스가 한번 로딩되면 변경되거나 언로딩 될 수 없다고 언급했지만 JRebel은 얼마든지 새 클래를 로딩할 수 있다. 이런 클래스 릴로딩을 이론적으로 이해하고자 먼저 자바 플랫폼에서 동작하는 동적 언어를 살펴보자.

-중략-

JRebel은 그 방법을 사용하지 않았다. 그보다 훨씬 복잡한 방법을 채택했다. 고급 컴파일 기술에 기반하여 마스터 클래스를 하나 사용하고 JIT Transformation 런타임이 지원하는 여러 익명 지원 클래스를 사용한다. 이렇게 해서 별다른 성능이나 호환성 문제 없이도 클래스를 갱신할 수 있다. (이 부분이 젤 궁금했는데 설명이 고작 세줄..이라니.. @_@;;)

클래스 릴로딩을 넘어서 – 아카이브

JRebel 2.x에서 사용자가 압축한 애플리케이션과 모듈을 작업중인 폴더와 매핑할 수 있는 rebel.xml 설정 파일을 사용할 수 있게 됐다. 아카이브로 배포하더라도 작업중인 소스를 수정하면 그게 반영된다.

workspace-map

메이븐을 사용하면 rebel.xml을 직접 만들 필요조차 없다.

클래스 릴로딩을 넘어서 – 설정과 메타데이타

애플리케이션에 존재하는 설정파일(ex, 스프리이 설정파일)이 달라지면 애플리케이션 동작도 그에따라 달라져야 한다.

conf

따라서 JRebel은 오픈 소스 API를 제공하여 써드파티 개발자들이 JRebel 기능을 사용해서 프레임워크 관련 설정이 변경됐을 때 애플리케이션에 반영될 수 있는 플러그인을 만들 수 있게 해준다. 예를 들어 스프링 빈과 의존성 설정이 바뀌면 애플리케이션에 반영되도록 지원하고 있다.