Real Time Java

Java SE 2008/11/26 18:19 Posted by Sun

기고인 : 최상훈 소프트웨어 엔지니어
(vicdev@empal.com)

CORBA ORB 및 Naming, Notification 서비스를 개발/설계.
EAI, BPM 및 메시징 시스템 개발 프로젝트에 참여.
http://objectworld.org에서 몇 년간 방법론, 아키텍처, 디자인 패턴 등을 스터디 했으며,
현재 JCO 회장을 맡고 있음.

빨리 퇴직해서 오픈소스 프로젝트를 소일 삼아 무위도식하는 게 꿈인 개발자.

------------------------------------------------------------------------------------------------------------------

2008 JavaOne에 가서 가장 흥미로왔던 강의가 Real-Time Specification for Java™: The Revolution Continues였다. JSR-001번을 차지하고 있고, 1998년에 시작하여 현재까지 11년이 지나도록 정식 자바 표준에 들지 못 한, 이 끈질긴 스펙은 어쩌면 자바가 넘어야 할 산을 암시하는 복선이었을지도 모른다. 그만큼 자바라는 환경에서 Real-Time을 구현하기가 힘들다는 얘기이다. 실제로 Real-Time Specification for Java(이하 RTSJ) 스펙의 Preface to the First Edition 챕터는 Dreams와 Realization 등의 주제로 구성되어 있다.

최근에 몇몇 밴더들이 RTSJ를 구현한 제품이 발표하면서 사람들의 관심이 RTSJ에 다시 모아진다. 하지만, 아직도 많은 넘어야 할 산이 남은 이 스펙은 한번쯤 눈여겨볼만 하다. 왜냐하면 앞으로의 컴퓨터 패러다임은 엔터프라이즈에서, 웹 서비스로, 그리고 다시 우리의 실생활 곳곳으로 스며들고 있기 때문이다. (참고: 2008 자바원 - 생생한 현장 소식 Java+You) 그리고, 그 곳곳에 포진한 자동차 제어 시스템, 모바일 플랫폼 등과 같은 곳에서는 임베디드 + Real-Time이 지배하는 세계이기 때문에 더더욱 관심을 가져볼만 하다. (참고로, 모든 임베디드 운영체제가 리얼타임 운영체제는 아니지만, 대부분의 리얼타임 운영체제는 임베디드 운영체제이다. 보통 작은 디바이스에 탑재되어 제어관리를 하는데 많이 사용되기 때문이다. 생각해보면, 실세계는 일반적인 컴퓨터 처리방식이 아닌, 비동기적이며, 실시간 적이다.)

필자가 처음으로 Real-Time을 공부하기 시작한 때는 Real-Time CORBA를 개발하기 위해 연구했을 때이다. 참고 연구과제로 삼았던 게 RTSJ였는데, 당시 RT CORBA보다 더 재밌게 공부했던 기억이 난다.

어쨌든 이번 JavaOne에서 RTSJ 강연을 두 개나 들었고, 언젠가 한번 Java의 Real-Time을 다뤄보고 싶었는데, 막상 RTSJ의 깊은 곳을 다루자니 사전 지식을 요해 많은 분들이 안읽을 것 같고, RTSJ의 기본 개념을 소개하자니 이미 훌륭한 소개서들이 많아 동어반복적인 기고가 우려되어 주제 선정이 힘들었다. 그래서 모든 RTSJ 소개서에서 항상 설명하지만 기초 개념이 필요해 감동을 주지 못 하는 부분을 공략하기로 했다.


Real-Time

우리가 말하는, 흔히 “실시간으로 처리한다.”는 표현에서의 ‘실시간’은 사실은 실시간적이지 않다. 실시간이란 정해진 시간안에 처리가 보장되는 것을 말한다. 이 두 개념의 차이는 이렇다. 이를테면 빵공장에서 밀가루 반죽을 10초 동안 분출한 후 2초 동안 중지하는 제어 시스템이 있다고 했을 때, 일반 운영체제를 이용하면 반죽의 양이 항상 정확하게 똑같이 나오지는 않는다. 왜냐하면 선점형 스페줄링하에서도 메모리를 할당하는 동작, 커널 리소스를 생성/해제하는 동작, 페이징, 스와핑 등 다른 태스크를 실행하기 위해 인터럽트될 수 없는 상태가 발생하기 때문이다. 게다가 워스트 케이스가 발생하여 추가 처리시간이 필요한 경우를 고려한다면 ‘정해진 시간안에 동작하기’란 상당히 어려운 주문이 된다. 따라서 10초 안에 빠르게 미리 처리해놓는다고 해도 ‘정해진 시간안에 동작하기’란 조건을 만족하기 힘들다.

그런데 만약 위의 시스템이 원자력 에너지 제어 시스템이라고 가정해보자. 곧바로 그 지역은 체르노빌이 될 것이다. (이와 같은 중요성 때문에 다른 문서에서는 원자력, 국방, 항공 제어 시스템을 예로 많이 든다.) 그래서 실시간 시스템에서 필요한 것이 ‘예측성’이다. 실제로 하드 리얼타임 시스템은 정해진 시간 안에 수행할 수 없는 태스크는 아예 수행조차 시키지 않는다. 즉, 태스크가 실행될 조건(환경)을 계산해서 수행 가능한지 예측하는 것이다. 그래서 실시간 운영체제는 처리율이 더 낮다.
하드 리얼타임에 대해서 좀 더 얘기하자면, 태스크의 수행 시간을 엄격하게 준수하는 것을 말한다. 반면 소프트 리얼타임은 엄격성이 떨어진다. 즉, 수행 시간을 준수하지 못 해도 수행이 된다는 말이다.

RTSJ에서는 자바의 많은 장점들이 곧 단점이 된다. 강력한 쓰레드 지원, 가비지 컬렉션, WORA, 동적 객체 바인딩… 이제 RTSJ가 어떤 문제점들을 어떻게 해결하고 있는지 알아보기로 하자.


운영체제 스케줄링

가끔 참담한 생각이 드는 것이.. 우리에게 절대 전능한 JVM이, 운영체제 입장에선 하나의 프로세스밖에 안 된다는 것이다. 웹 애플리케이션 서버라도, 어떤 강력한 자바 플랫폼이라도 JVM이 실행하는 하나의 애플리케이션이고, JVM은 OS 상에서 하나의 프로세스일 뿐이라는 것이다.
우리가 아무리 쓰레드 스케줄링을 잘 해도 운영체제 스케줄러가 CPU를 할당해주지 않으면 그 시간만큼은 동작하지 않는다. JVM이 OS에 의해 스케줄되지 않으면 실행되지도 못하므로 RT를 적용할 기회조차 잃게 된다.

이것은 어쩌면 당연한 일이지만, Real-Time Java에게는 치명적인 약점이 된다. 위의 예제에서 2초 후 다시 동작해야 하는 시점에 운영체제 스케줄러가 CPU를 JVM이란 프로세스에 할당하지 않으면 절대 실행되지 않기 때문이다.

그래서 RTSJ는 대부분 실시간 운영체제 상에서 동작한다.


가비지 컬렉션

실제로 RTSJ의 문제의 대부분은 메모리와 스케줄러의 문제이다. 그리고, 이 두 가지의 문제를 중첩해서 갖고 있는 것이 가비지 컬렉터(GC)이다. 가비지 컬렉션이 이뤄지는 동안은 어떤 메모리를 참조하는 동작도 locking이 걸리기 때문이다. (이 말은 거의 모든 동작이 중단된다는 얘기이다.) 물론 현대 JVM은 여러 알고리즘을 통해 이 GC 시간을 줄이고 있으며, 최적화 수준은 상당히 높다. 다음은 반복해서 어떤 작업을 실행하는 동안 주기적으로 발생하는 GC 시간에 대한 그래프이다. 이 GC 시간 동안은 거의 모든 동작이 정지된다.
 

[그림 1: Fully Time Deterministic Java에서 발췌]

하지만, RTSJ에서는 GC 시간이 없어야 하는 무리한 전제가 필요하다. (줄이는 것과 없애는 것은 이 주제에서 큰 차이다. 1 나노세컨드 동안 실행 중단도 Real-Time 세계에서는 용납되지 않는다.) 아래 그림에서처럼 P1의 작업이 t1 시점에 모두 완료해야 한다고 했을 때, 불행히도 GC가 발생하여 작업이 지연되었다. 이렇다면, 하드 리얼타임을 보장할 수 없다. GC 시간이 아무리 우연히, 절묘하게 태스크 완료 시점을 방해했다고 해도 Real-Time 상황에서는 엄격하게 가정할 수 없는 일이다. 즉, 데드라인을 지키기 위해서는 무중단 실행을 보장해야 한다.
 

GC 시간을 없애는 방법은 GC를 사용하지 않는 방법 밖에 없다. 하지만, 이런 설정은 비약적인 표현이다. GC가 없는 자바는 상상할 수 없을 테니까 말이다. 그래서 RTSJ에서는 GC되지 않는 메모리와 GC되는 메모리를 분리하고 있다.

아래는 위 [그림 1]의 실험을 동일한 상황에서 IBM Metronome GC를 이용해서 테스트한 것이다.
 

[그림 3: Fully Time Deterministic Java에서 발췌]


Real-Time 쓰레드


Java Language Specification에 의하면 쓰레드 우선순위는 반드시 보장되지 않는다. (적어도 2nd Edition 17.12 Threads에서는 그렇게 적시돼있다.) 하지만 Real-Time 시스템에서는 쓰레드 우선순위를 엄격하게 준수해야 한다. 왜냐하면 데드라인 안에 꼭 실행돼야 하는 우선순위가 높은 쓰레드가 있는데, aging 정책 때문에 우선순위가 낮은 쓰레드에게 스케줄링을 양보하게 되면 곤란하기 때문이다. 따라서, RTSJ에서는 RealtimeThread 와 NoHeapRealtimeThread라는 새로운 쓰레드 타입을 제공한다.

RealtimeThread는 non-heap 메모리를 접근하거나, 비동기 호출, 실시간으로 스케줄링되기 위해 사용되는 쓰레드이다. NoHeapRealtimeThread는 말 그대로 Heap에 할당되지 않는 쓰레드를 말한다. 이 쓰레드는 힙을 사용하지 않기 때문에 GC 시간에도 동작한다. 이렇게 RT 쓰레드는 메모리와 밀접한 관계를 가지는데, 주로 GC 시간 동안에도 동작하기 위해서 GC되지 않는 메모리를 접근해야 하기 때문이다. 따라서 RTSJ에서는 아래와 같이 메모리와 쓰레드 간의 영역을 구분하는 모델을 갖고 있다.
 

[그림 4: The RTSJ and Project Mackinac에서 발췌]

또한, 자바는 어떠한 플랫폼에서도 동작해야 하기 때문에 별도의 우선순위 레벨을 정의한다. 실시간 운영체제마다 우선순위를 다르게 정의하고, 우선순위를 처리하는 방식이 다르기 때문이다. LynxOS, VxWorks, Nucleus, VRTX는 0부터 255까지 256개의 우선순위를 설정할 수 있으며, 0이 가장 높다. 하지만, pSOS도 똑같이 256개의 우선순위를 가지지만 0이 가장 낮다. uC/OS의 경우 독특하게도 0부터 63까지의 우선순위를 가진다. 이렇게 서로 다른 우선순위를 정의하고, 처리하기 때문에 RTSJ는 28 레벨의 우선순위를 별도로 정의했다.

끝으로, 일반 쓰레드 모델과 Real-Time 쓰레드와 다른 점은 priority inversion(우선순위역전)에 있다. 보통 우선순위가 높은 어떤 쓰레드가 ServerSocket.accept(), Socket으로부터 얻은 InputStream을 read()할 경우와 같이 이벤트가 발생할 때까지 기다려야 하는 함수를 실행하면 우선순위가 낮더라도 다른 쓰레드로 CPU 제어를 넘긴다. 왜냐하면, 기다리는 동안 CPU를 사용할 이유가 없기 때문이다. 문제는 이 기다리는 (wait() 함수 호출) 상태가 notify 됐을 때 곧바로, CPU를 선점하지 못 하는데 있다. 이 경우는 낮은 우선순위의 쓰레드가 CPU를 양보할 수 없는 상황에 있을 때이다. 가장 대표적인 경우가 lock을 쥐고 있는 경우다.

이런 이유로 데드라인을 지키지 못 한다면, Real-Time 상황에서는 문제를 일으킬 수 있다. 이런 경우를 priority inversion이라고 하는데, 우선순위가 실제로 낮아지는 것이 아니라 CPU를 양보하는 것이다. Real-Time 쓰레드 모델은 priority inversion에 의해 높은 우선순위의 쓰레드가 실행되지 못 하는 것을 방지하고 있다.


Class loading, initialization, 그리고 compilation

자바 클래스가 실행될 때, 실제로 그 클래스는 메모리에 이미 로딩되어 있지 않다. Lazy load를 통해 초기 부트스트랩 시간을 줄이기 위한 목적도 있고, 사용하지 않을지도 모르는 클래스를 굳이 미리 로드할 필요가 없기 때문이기도 하다. 끝으로 클래스가 동적으로 링크되는 상황이라면 로드 자체를 할 수도 없다.

따라서 애플리케이션에서 클래스 A가 클래스 B를 첫 번째로 참조했을 때, 이때 클래스 B는 로드된다. 문제는 로드되는 시간이다. 디스크 속도에 따라 미세하지만 로드 시간의 차이가 발생할 수 있다. Real-Time 환경에서는 어떠한 상황에서도 태스크가 항상 똑같이 실행되는 것을 전제로 하는데 ZFS, Ext3, NTFS 와 같이 파일 시스템의 종류에 따라 로딩 시간이 결정되거나, 심지어 하드 디스크 종류에 따라 로딩 시간이 지연되면 문제가 발생한다.

실행 시간은 프로그램 로드 후 이뤄지기 때문에, 컴파일 언어에서는 메모리에 모두 로드하고 실행시키기에 - 솔직히 이렇지는 않다. – 문제가 없지만, 자바와 같이 동적으로 로딩하는 언어는 문제가 생길 수 있다. RTSJ에서는 이런 문제를 해결하기 위해 실행될 소스코드를 트래킹한다. 즉, 첫 번째 실행 시 클래스를 로드하는 방식이 아니라 앞으로 실행될 클래스를 미리 계산해서 미리 로드해두는 방식이다.

또한, 자바는 half-compiled, half-interpreted 언어이기 때문에 컴파일 시 byte 코드로 만든 후 실행 시 인터프리팅하여 네이티브 코드를 만드는 구조를 갖고 있다. 역시 여기서의 문제는 인터프리팅 시간이 지연되는데 있다. 그 밖에도 코드 검사, 보안 정책 설정 등 많은 작업들이 실행된 후에야 클래스가 실행 가능한 객체로 initialization 된다. 이를 위해 어떤 RTSJ 구현은 미리 컴파일하는 방법을 채택하기도 한다.

맺음말

이와 같이 RTSJ는 자바가 갖고 있는 우수한 점들을 모두 부정해야 하는 상당히 모순된 전제조건을 가지고 있다. 하지만, 자바가 점령해야 할 어딘가에는 Real-Time이란 개척지가 있으며, Real-Time을 요구하는 분야는 생각보다 크다. 따라서 앞으로도 RTSJ는 투쟁하는 모습을 보일 것이며 언젠가는 JSR에서 정식 표준으로 자리 잡기를 기대해본다.


"Java SE" 카테고리의 다른 글

2008/11/26 18:19 2008/11/26 18:19

TRACKBACK :: http://blog.sdnkorea.com/blog/trackback/678

  1. 하얀말의 생각

    Tracked from ryudaewan's me2DAY  삭제

    Real Time Java. 글쓴이의 꿈이 나랑 비숫한걸?

    2008/12/23 12:26

댓글을 달아 주세요

  1. 임준민  수정/삭제  댓글쓰기

    좋은 글 감사히 읽었습니다

    딴지 걸자면 '운영처제'가 아니라 '운영체제'입니다

    2008/12/16 19:06
    • Sun  수정/삭제

      수정했습니다.

      감사합니다. ^^

      2008/12/26 10:16
[로그인][오픈아이디란?]

◀ Prev 1  ... 176 177 178 179 180 181 182 183 184  ... 806  Next ▶