Real-time Java, Part 5: 실시간 자바 애플리케이션의 작성과 전개
예제, 힌트, 팁
Real-time Java™ 시리즈 , 다섯 번째 글에서는 IBM?? WebSphere?? Real Time에서 제공한 툴을 사용하여 실시간 자바 애플리케이션을 작성 및 전개하는 방법을 설명합니다. 필자는 샘플 애플리케이션을 사용하여 가비지 컬렉션 중지를 제어하는 Metronome 가비지 컬렉터, 런타임 컴파일 중지를 피하는데 사용되는 Ahead-of-time 컴파일러, 엄격한 타이밍 요구 사항을 맞추는 NoHeapRealtimeThread 를 설명합니다.
본 시리즈 의 이전 기술자료에서는 IBM WebSphere Real Time이 매우 낮은 타임스케일로 낮아지는 비결정적 문제를 어떻게 해결하는지를 보았다. 이 기능은 자바 플랫폼의 범위로 확장되고 Ada 같은 특별한 실시간(RT) 프로그래밍 언어를 위해 보유한 영역에 혜택을 준다. RT 하드웨어와 OS는 종종 커스터마이징 되고 신비롭다. WebSphere Real Time은 IBM BladeCenter?? LS20과 유사 하드웨어와 호환되는 RT 버전의 리눅스?玲【? 실행된다. 전형적인 RT 애플리케이션의 요구 사항들을 지원한다.
- Low latency: 정해진 시간 안에 시그널에 대한 응답을 보장한다.
- 결정론: 가비지 컬렉션(GC)으로 인한 중지가 없다.
- 예견 가능성: 쓰레드 우선 순위가 실행 순서를 관리하고, 실행 회수는 일정하다.
- 우선 순위 역전 없음: 높은 우선 순위의 쓰레드는 잠금을 보유하고 있는 낮은 우선 순위 쓰레드에 의해 블로킹 될 수 없다. 중간 우선 순위의 쓰레드가 실행되고 있기 때문이다.
- 물리적 메모리로의 액세스: 장치 드라이버 같은 RT 애플리케이션들은 금속에 가까이 가야 한다. (물리적 메모리에 액세스 해야 한다.)
이 글에서는 WebSphere Real Time에서 제공되는 툴을 사용하여 RT 자바 애플리케이션들을 작성 및 전개하는 방법을 설명한다. 본 시리즈의 이전 기술자료들을 참조할 것이며, RT 결정론 정도를 높이면서 프로그램을 실행하는 방법을 설명하겠다. (이전 기술자료를 참조하면 도움이 된다.) Metronome 같은 RT GC 정책을 사용하여 WebSphere Real Time에서 제공하는 Lunar Lander 샘플 애플리케이션의 예견 가능성을 높이는 방법도 설명한다. 또한, RT 환경에서 결정론 향상을 위해 Ahead-of-time (AOT) 컴파일 방법을 설명한다. 마지막으로, 가비지 컬렉터에 의해 제어되지 않는 메모리를 사용하여 RT 애플리케이션을 디자인 및 구현하고, RT 자바 애플리케이션을 활용하는 팁도 제공한다.
이 글에서 설명하는 프로그램을 실행하고 싶거나, 여러분 개인적인 RT 자바 애플리케이션을 작성하고 싶다면, 시스템에 WebSphere Real Time을 설치하라.
Metronome은 WebSphere Real Time의 가비지 컬렉터이다. WebSphere Real Time에서 제공되는 샘플 애플리케이션을 시작해보면 이것의 장점을 파악할 수 있다. WebSphere Real Time을 설치한 후에, 설치된 디렉토리 에서 /sdk/demo/realtime/sample_application.zip에서 이를 찾을 수 있다.
샘플 애플리케이션은 Lunar Lander 모듈의 컨트롤 기술을 모방했다. 안전하게 착륙하려면 Lander의 로켓 추진 엔진이 정확히 전개되어야 한다.
- 하락 비율을 감소시키는 수직 추진 엔진.
- 착륙 사이트로 정렬하는 수평 추진 엔진.
Lander 모듈의 위치를 계산하려면, Controller는 레이더가 리턴 하는데 걸린 시간을 사용한다.
리턴되는 신호에 지연이 발생하면(이를 테면, GC 중지로 인한), Lander의 위치는 부정확하게 계산된다. 레이더 펄스가 리턴 하는데 더 긴 시간이 걸렸다면 거리가 멀다는 것을 의미하고, Controller는 부정확하게 계산된 위치를 조정한다. 확실히, 이것은 Lander 또는 RT 시스템에 나쁜 결과를 가져온다.
표준 자바가 실행 RT 애플리케이션에 얼마나 적합하지 않은지를 보여주는 한 가지 방법은 Controller가 정확한 궤도로 Lander을 얼마나 정확하게 유지하는지를 계산하는 것이다. 그림 2의 그래프는 표준 자바 VM을 사용하여 Controller를 시뮬레이트 한 모습이다. 빨간 선은 Lander의 진짜 위치를, 파란색 선은 레이더가 측정한 위치를 나타낸다.
그림 2. 표준 자바 VM을 사용한 Controller 시뮬레이션

이 비행은 성공적인 착륙으로 끝났지만, 그림 2의 그래프는 레이더에 의해 측정된 높이에서 여러 개의 큰 스파이크(파란색 라인)를 보여준다. 이는 GC 중지에 해당한다. 어떤 경우, GC 중지는 과도한 착륙 속도(수직 위치 에러) 또는 착륙 장소 소실(수평 위치 에러)로 인한 충돌을 발생시킬 만큼 큰 에러의 원인이 된다. 이러한 비 결정적(nondeterministic) 런타임 작동 때문에 자바 플랫폼이 RT 애플리케이션에 사용되지 못했다.
Real-time Specification for Java (RTSJ)는 GC 중지 문제에 대한 다양한 솔루션을 제공한다. 자율 메모리 관리의 중요성을 인정하지만, 프로그래머가 메모리의 컨트롤을 다시 가져야 하는 GC 효과를 피하기 위해 새로운 메모리 영역을 도입해야 한다. NoHeapRealtimeThread s 관련 섹션에서 보았지만, 이는 신뢰성 있는 자바 애플리케이션을 작성에 많은 문제를 가져온다. 매우 짧은 중지를 참을 수 있는 많은 RT 애플리케이션들에 맞는 대안 방식은 WebSphere Real Time의 Metronome 같은 RT 가비지 컬렉터를 사용하는 것이다.
Metronome으로 Lunar Lander 애플리케이션을 실행하면 Lander의 진짜 위치를 훨씬 흡사하게 따라가는 그래프가 만들어진다. 높이 측정과 연착륙 시 중대한 스파이그가 없다. (그림 3)
그림 3. WebSphere Real Time을 사용한 Controller 시뮬레이션

두 번째 실행에서, Controller의 자바 코드는 바뀌지 않았다. 이는 RT 가비지 컬렉터의 혜택을 받는 정상적인 J2SE 애플리케이션이다.
-verbose:gc 매개변수를 샘플 호출에 추가하여 감소된 GC 중지의 더 많은 상세를 보여줄 수 있다.
이 예제는 데모 실행에서, 1초 간격으로 GC 액티비티를 보고하고 있다. GC가 171회 실행되었고( quantumcount ) 점증적인 GC 중지로부터 애플리케이션이 받은 미미한 중지 시간( meanms )은 0.470 밀리초이다.
애플리케이션 작업과 GC 중지의 인터리빙(interleaving)의 경우, Metronome 트레이스 파일을 기록하고 이를 TuningFork 분석 툴로 나타낼 수 있다. (그림 4)
GC 중지가 최소화 되면, 실행 애플리케이션에 혼란을 야기할 수 있는 다른 요소들이 더욱 뚜렷해진다. 이 중 하나가 Just-in-time (JIT) 컴파일러의 액티비티이다. 자바 바이트코드를 네이티브 코드로 컴파일 하는 것은 좋은 성능을 위한 필수 요소이지만, 네이티브 코드 생성은 중지를 일으킬 수 있다. 이 문제에 대한 해결책은 AOT 컴파일을 사용하여 자바 바이트코드를 사전 컴파일 하는 것이다.
자바 런타임은 JIT 컴파일러를 사용하여 자바 애플리케이션 내에서 가장 빈번하게 실행되는 메소드를 위해 네이티브 코드를 동적으로 만들어 낸다. RT 환경에서, 엄격한 데드라인을 갖고 있는 일부 애플리케이션들은 동적인 컴파일 액티비티와 관련이 있는 비결정론을 견딜 수 없다. 복잡한 애플리케이션을 시작하는데 사용되는 많은 메소드들을 컴파일 하는 오버헤드는 바람직하지 못하다. 이러한 문제에 직면한 애플리케이션 개발자들은 AOT 컴파일을 사용한다.
|
|
|
AOT 컴파일에는 애플리케이션이 실행되기 전에 애플리케이션의 자바 메소드용 네이티브 코드를 생성하는 것이 포함된다. 이렇게 하면 사용자는 동적 컴파일의 비결정론을 피할 수 있고, 네이티브 컴파일과 관련한 성능 이득도 볼 수 있다. 실행하고 있는 AOT-컴파일(사전 컴파일) 코드는 동적인 JIT 컴파일러로 실행되는 것 보다 약간 느리다. 정적인 본성 때문에, 사전 컴파일 된 코드는 JIT 컴파일러에 의해 동적으로 생성된 코드와는 달리 자주 사용되는 메소드의 최적화 혜택을 받을 수 없다. WebSphere Real Time은 현재 동적인 JIT 컴파일과 사전 컴파일 코드의 혼합을 허용하지 않는다. 요약하면, AOT 컴파일은 보다 결정적인 런타임 성능을 제공한다.
최적화를 위해 JIT 컴파일러가 사용하는 기술, JIT와 AOT 컴파일러의 장단점, 비교 등 자세한 내용은 " Real-time Java, Part 2: 컴파일 기술 비교 "를 참조하라.
AOT 컴파일 툴인 jxeinajar 는 JAR 또는 ZIP 파일 포맷으로 저장된 클래스에서 네이티브 코드를 생성한다. 이 툴은 JAR 파일의 클래스에 있는 모든 메소드 또는 선택된 일부 메소드를 위해 AOT-컴파일 코드를 만들 수 있다. AOT-컴파일 코드는 고정된 최적화 레벨을 사용할 경우 JIT 컴파일러가 생성하는 네이티브 코드와 같다. 이 코드는 Java eXEcutable (JXE)로 알려진 내부 포맷에 저장된다. jxeinajar 툴은 JXE 파일을 JAR 파일로 래핑하는데, 그리고 나서 WebSphere Real Time이 실행할 수 있다.
|
|
|
컴파일은 2 단계 프로세스이다. 첫 번째 단계인 AOT 코드 생성 단계( jxeinajar 툴 사용)에서는 AOT 컴파일러를 사용하여 네이티브 코드를 생성한다. 두 번째 단계는 Java Runtime Environment (JRE) 내에서 코드를 실행하는 단계이다.
다음 명령어(여기에서 aotJarPath 는 사전 컴파일된 파일이 작성될 디렉토리이다.)는 현재 디렉토리에 있는 모든 JAR 또는 ZIP 파일을 위해 AOT-컴파일 코드를 생성하고, $JAVA_HOME/bin이 $PATH 에 있는 것으로 간주한다.
명령어를 실행한 후에, 다음과 같은 결과를 볼 수 있다.
생성된 JAR 파일은 진정한 JAR가 아니다 . 클래스 파일이 포함되어 있지 않다. 대신, 모든 클래스와 플레이스홀더 클래스 파일을 위한 JXE 파일이 포함되어 있는데, 이것은 네이티브 코드에 액세스 하는데 사용된다. 이러한 파일들은 다른 자바 런타임 툴과 함께 사용될 수 없고 WebSphere Real Time 버전에만 해당한다.
개별 JAR나 ZIP 파일은 명령행에 지정되어 기본 작동을 오버라이드 한다. 인풋 파일에 대한 검색을 확장하여 하위 디렉토리를 포함시키려면, -recurse 옵션을 명령어에 추가한다.
jxeinajar 툴에 의해 생성된 파일 포맷은 JXE 파일과 JXE 파일 내에 있는 개별 파일들에 대한 효과적인 포인터가 되는 것을 포함하고 있다. JAR 또는 ZIP 파일의 콘텐트를 리스팅 함으로써, jxeinajar 툴이 이 파일을 생성하는지를 빠르게 파악할 수 있다. demo.jar를 검사하고 싶다면, 콘텐트를 리스팅 하는 명령어는 다음과 같다.
jxeinajar 에 의해 생성된 JAR 파일은 다음과 같은 결과를 만들어 낸다.
JAR 파일 내의 추가 JXE 파일은 jxeinajar 툴이 생성했던 JAR 파일로서 이를 구분한다. 그렇지 않을 경우, 아웃풋은 다음과 같다.
애플리케이션은 AOT 컴파일 시키면 다음 명령어를 사용하여 이것을 실행할 수 있다.
|
|
|
동적 JIT 컴파일과 AOT 컴파일은 여러분이 WebSphere Real Time을 사용할 때 혼합될 수 없다. -Xnojit 옵션을 제거하면, Java VM에 사용할 수 있는 AOT 컴파일 코드가 사용되지 않는다. 대신, 이 코드는 인터프리팅 되거나 JIT에 의해 동적으로 컴파일 된다. 명령어에 있는 -Xrealtime 옵션은 RT Java VM을 실행한다. 이 옵션을 제공하지 않으면, WebSphere Real Time에 포함된 SE Java VM이 사용된다.
-Xnojit 플래그가 설정되면, WebSphere Real Time은 인터프리터를 사용하여 사전 컴파일 되었던 메소드를 실행한다. 이것이 먼저 사전에 컴파일 되지 않았던 애플리케이션 코드의 버전을 사전 컴파일 된 JAR 파일이나 Classpath에 지정된 다른 JAR 파일에서 찾으면, 이 코드는 인터프리팅 된 속도로만 실행된다.
애플리케이션을 사전 컴파일 할 뿐만 아니라, 키 시스템 파일들을 AOT로 컴파일 할 것을 권한다. 표준 자바 API를 사용하는 애플리케이션은 시스템 JAR 파일들이 컴파일 되지 않는 한 부분적으로만 컴파일 된다. 대부분의 표준 API 클래스들은 core.jar와 vm.jar 파일 들에 저장되기 때문에 이러한 두 개의 파일들을 시작 포인트로서 AOT-컴파일 할 것을 권하는 것이다. RT 애플리케이션의 경우, realtime.jar 역시 사전 컴파일 해야 한다. 이 외에도, 애플리케이션은 어떤 추가 시스템 파일들이 사전 컴파일 됨으로써 성능의 이득을 볼 수 있는지를 결정한다.
시스템 JAR 파일들을 AOT-컴파일 하는 프로세스는 다른 JAR 파일을 AOT-컴파일 하는 것과 동일하다. 하지만, 시스템 JAR 파일들은 부트 Classpath에서 로딩되므로, 다음 명령어를 사용하여 사전 컴파일 된 시스템 JAR 파일들을 부트 Classpath에 prepend 하여 이들이 사용되도록 한다.
-Xbootclasspath/p: 옵션의 /p 는 사전 컴파일 된 시스템 JAR 파일들을 부트 Classpath에 prepend 한다. 이 부트 Classpath 역시 -Xbootclasspath: 와 -Xbootclasspath/a: 옵션 (각각, 설정과 첨부)으로 조작될 수 있다. 하지만 -Xbootclasspath: 또는 -Xbootclasspath/a: 를 사용하여 부트 Classpath에 AOT-컴파일 JAR 파일을 둔다면, 컴파일 된 클래스는 사용되지 않을 것이다.
이 Classpath에서는 거의 실수하지 않는다. 애플리케이션이 여러 JAR 파일들로 이루어지고 시스템 JAR 파일들을 사전 컴파일링 할 경우 특히 그렇다. 실수는 예상했던 사전 컴파일 된 코드 대신에 사전 컴파일 되지 않은 코드를 실행시키는 것이다. 다음 옵션들을 결합하면 여러분이 사용하고 있는 클래스들이 사전 컴파일 되는 지를 확인할 수 있다.
- -verbose:relocations 는 사전 컴파일 된 코드에 대한 재할당 정보를 STDERR 에 프린트 한다. 사전 컴파일 된 메소드가 실행될 때마다 로그 메시지가 프린트 된다. 이 옵션의 결과는 다음과 비슷하다.
Relocation: realTimeApp.main([Ljava/lang/String;)V Time: 10 usec
- -verbose:class 는 로딩 될 때 각 클래스에 대해 메시지를 STDERR 에 작성한다. 이 옵션은 다음과 같은 결과를 만들어 낸다.
class load: java/lang/Object class load: java/lang/J9VMInternals class load: java/io/Serializable class load: java/lang/reflect/GenericDeclaration class load: java/lang/reflect/Type class load: java/lang/reflect/AnnotatedElement
- -verbose:dynload 는 자바 VM에 의해 로딩된 각 클래스에 대한 상세 정보를 제공한다. 이 정보에는, 클래스 이름, 패키지, 클래스 파일 위치가 포함된다. 이 정보의 포맷은 다음과 비슷하다.
안타깝게도, 이 옵션은 사전 컴파일 된 JAR 파일에서 클래스들을 리스팅 하지 않는다. 하지만, 이것을 -verbose:class 옵션과 결합하면, 어떤 클래스들이 사전 컴파일 되는지를 추론할 수 있다. -verbose:dynload 아웃풋이 아닌 -verbose:class 아웃풋에 리스팅 된 클래스는 사전 컴파일 된 JAR 파일에서 로딩되어야 한다. 여러분에게 필요한 verbose 옵션은 -verbose:class,dynload 이다.
애플리케이션이 자주 사용하는 메소드의 프로파일을 생성하고 그러한 메소드들을 AOT-컴파일 함으로써 더욱 최적화 된 사전 컴파일 된 JAR 파일들을 구현할 수 있다.
-Xjit:verbose={precompile},vlog=optFileName 옵션 ( optFileName 은 사전 컴파일 하고자 하는 메소드를 리스팅 하는 파일의 이름이다.)으로 애플리케이션을 실행함으로써 프로파일을 동적으로 생성할 수 있다.
이 옵션은 애플리케이션이 실행하는 동안 JIT 컴파일러가 컴파일 했던 메소드에 상응하는 메소드 서명 리스트를 포함하고 있는 옵션 파일들을 생성한다. 필요할 경우 텍스트 에디터로 이 파일을 쉽게 편집할 수 있다. 이 파일을 jxeinajar 툴에 제공하여 어떤 메소드가 사전 컴파일 되는지를 제어할 수 있다. 다음 명령어를 사용하여 툴에 파일을 제공한다.
WebSphere Real Time에서 제공하는 InfoCenter에서도 사전 컴파일 된 AOT 컴파일에 대해 설명하고 있다. 이전 섹션의 Lunar Lander의 런타임 프로파일을 생성하는 것부터 이 프로파일을 사용하여 Lunar Lander 애플리케이션과 시스템 JAR 파일을 선택적으로 사전 컴파일 하는 방법들을 설명하고 있다. 다른 애플리케이션을 사전 컴파일 하고 싶다면 다음 섹션에서 설명 할 Sweet Factory 애플리케이션도 사용할 수 있다.
WebSphere Real Time에는 RTSJ의 전체 구현이 포함되어 있다. RTSJ는 Metronome 같은 RT 가비지 컬렉션 전에 디자인 되었고, 자바 런타임에서 예견 가능하고, 낮은 레이턴시의 성능을 달성할 수 있는 다른 방법이 포함되어 있다.
RTSJ를 작성할 때, 자바 런타임에서 예견 가능한 실행의 가장 큰 장애물 두 개는 JIT 컴파일러와 가비지 컬렉터였다. 이들 각 기술들은 애플리케이션 프로그래머의 컨트롤 밖에서 프로세서 타임을 사용한다. 이들의 동적인 성향 때문에 예견하지 못한 지연이 자바 애플리케이션에 생긴다는 점이다. 어떤 경우, 이러한 지연은 수 초간 지속되는데, 이는 많은 RT 시스템에서는 허용될 수 없다.
JIT 컴파일러는 AOT 컴파일 같은 기술로 대체될 수 있지만, GC는 쉽지 않다. 이것이 제거되기 전에, 대안 메모리 관리 솔루션이 제공되어야 한다.
표준 가비지 컬렉터에서 기인한 지연을 감당할 수 없는 RT 시스템을 지원하기 위해, RTSJ는 영구( immortal ) 및 한정( scoped ) 메모리 영역을 정의하여 표준 자바 힙을 보완한다. RTSJ는 또한 새로운 두 개의 쓰레드 클래스, RealtimeThread 와 NoHeapRealtimeThread (NHRT)를 지원하는데, 이는 애플리케이션 프로그래머들이 힙 보다는 메모리 영역 사용 같은 다른 RT 기능들을 활용할 수 있도록 해준다.
NHRT는 자바 힙에서 생성된 객체들로 작업할 수 없는 쓰레드이다. NHRT는 이 객체들이 가비지 컬렉터와 독립적으로 실행되어 낮은 레이턴시의 예견 가능한 실행을 할 수 있도록 한다. NHRT는 한정이나 영구 메모리를 사용하여 객체를 생성해야 한다. 여기에는 표준 힙 기반 자바 프로그래밍에 사용되었던 것 과는 완전히 다른 프로그래밍 스타일이 필요하다.
이제 NHRT를 사용하여 간단한 애플리케이션을 개발하여 NON-HEAP 메모리와 관련한 프로그래밍 문제들에 대해 알아보도록 하자.
사탕 공장의 자동화 시스템을 구현할 것이다. 미가공 원료를 다양한 종류의 사탕들로 만들 것이다. 미가공 원료를 다양한 종류의 사탕들로 만든 다음 이들을 항아리에 담는 여러 생산 라인들이 이 공장에 있다. 시스템은 항아리에 너무 많거나 적게 사탕들이 담기지 않았는지를 검사하고 작업자들에게 잘못 채워진 항아리를 가져올 것을 공지한다.
항아리가 채워진 후에, 각 항아리에 로딩 되었던 사탕의 수를 검사하기 위해 무게를 단다. 항아리에 있는 사탕의 수가 목표치의 2 퍼센트 밖에 있다면, 작업자의 컨트롤 스크린에 문제를 알리는 메시지가 보내진다. 작업자는 컨트롤 패널에 디스플레이 된 항아리 아이디를 사용하여 항아리를 찾고 이를 패킹 큐에서 제거한 다음, 컨트롤 패널을 확인한다. 검사를 위해서 각 항아리의 크기가 로그 파일에 작성되어야 한다.
그림 5는 예제 시나리오의 다이어그램이다.
그림 5. 사탕 공장 시나리오
확실히, 이 예제는 다소 작위적이지만 NHRT 애플리케이션을 만들고, NHRT와 다른 쓰레드 유형들간 데이터를 공유하는 문제를 파악하는데 도움이 된다.
시스템은 세 개의 외부 엔터티 클래스를 다루어야 한다. 생산 라인의 저울, 작업자 콘솔, 감사 로그이다. 생산 라인과 작업자 콘솔은 시스템에서 제공하는 자바 인터페이스에 캡슐화 되었다.
저울에 대한 인터페이스는 한 개의 메소드 --- weighJarGrams() --- 를 갖고 있는데, 이는 다음 항아리가 눈금을 벗어날 때까지 차단하고 항아리의 부피를 그램 수로 리턴한다. 항아리가 도착하는 비율은 다양하다. 하지만 생산 비율을 맞추기 위해서는 10 밀리초 마다 한 개의 항아리가 채워진다. weighJarGrams() 메소드가 충분히 자주 폴링(poll)되지 않으면 항아리를 잃게 된다.
저울은 생산 라인의 컴포넌트로서, 생산되는 사탕의 유형과 채워지는 항아리의 크기를 쿼리하는 메소드를 갖고 있다.
작업자의 콘솔은 두 개의 메소드 -- jarOverfilled() 와 jarUnderfilled() -- 를 갖고 있고 두 개 모두 항아리 jar ID를 갖고 있다. 이 메소드는 작업자가 메시지를 확인할 때까지 차단한다.
우리는 MonitoringSystem 인터페이스를 구현할 것이다. 이것은 두 개의 메소드, startMonitoring() 과 stopMonitoring() 을 갖고 있다 startMonitoring() 메소드는 ProductionLine 객체와 WorkerConsole 객체를 취한다.
감사 로그는 audit.log 라고 하는 플랫 파일로 지정되는데, 여기에서 각 라인은 timestamp,jar id,sweet type code, jar size code,mass of jar 와 같이 콤마로 분리된 스트링 포맷을 취하고 있다.
그림 6은 이러한 인터페이스를 보여주는 UML 클래스 다이어그램이다.
이제 스팩이 생겼으니 솔루션을 디자인 할 수 있다. 문제는 두 가지로 나뉜다. 하나는 생산 라인을 폴링하고 항아리 부피를 검사하는 것이고, 두 번째는 감사 로그를 작성하는 것이다.
WeighingMachine 인터페이스를 고려한다면, weighJar() 메소드는 자주 폴링되어야 하기 때문에 각 ProductionLine 은 확장성 있는 디자인을 위해 전용 쓰레드를 가져야 한다. 우리는 NHRT를 사용하여 가비지 컬렉터에 의해 인터럽트 되는 폴링 쓰레드 가능성을 최소화 할 것이다.
일단 항아리의 무게를 달았다면, 그 부피에 얼마나 많은 사탕들이 들어갈 수 있는지를 계산하고 이를 목표 값과 비교해야 한다. 측정을 위해 필요한 프로세싱의 양을 예견하는 것은 어렵다. 항아리에 있는 사탕의 수가 넘치면, WorkerConsole 과 통신해야 하는데, 이는 수 초의 시간이 걸린다.
항아리는 10 밀리초 간격으로 도착할 수 있기 때문에, 폴링 쓰레드를 계산할 수 없다. 우리는 개별 계산 쓰레드로 측정한 것을 전달해야 한다. 일부 프로세싱은 오랜 시간이 걸리기 때문에, 최근 측정을 통해 쓰레드를 사용할 수 있는지를 확인하려면 생산 라인 당 여러 프로세싱 쓰레드가 필요하다.
각 데이터 조각마다 새로운 쓰레드를 만들 수 있지만, 이는 쓰레드를 시작하고 중지하는 많은 프로세서 시간을 낭비한다. CPU 시간을 더 많이 사용하려면, NHRT 풀을 만들어서 데이터를 처리할 수 있다. 실행 쓰레드의 풀을 관리함으로써, 실행될 때 쓰레드 시작 및 중지 오버헤드를 없앤다.
모든 생산 라인에서 공유되는 하나의 쓰레드 풀을 가질 수 있지만, 여러 쓰레드에 의해 액세스 되는 데이터 구조는 동기화가 필요하다. 하나의 쓰레드 풀을 갖게 되면 중요한 잠금 경쟁이 발생한다. 확장성 있는 솔루션을 만들려면 각 생산 라인에는 고유의 쓰레드 풀이 부착되어야 한다.
쓰레드 풀 디자인에는 풀 사이징과 관리 기술 같은 많은 고려 사항이 들어간다. 이 글에서는, ProductionLine 객체에 10 개의 풀 쓰레드를 만들고 쓰레드 밖에서 실행될 경우 풀을 확장해야 한다.
시스템의 다른 컴포넌트와는 달리, 감사 로깅 컴포넌트는 시간이 중요하지 않다. 만일 우리가 컴퓨터 충돌이나 스위치 오프 가능성을 무시한다면, 유일하게 중요한 고려 사항은 측정이 어느 지점에서는 기록되어야 한다는 것이다.
이를 위해서 우리는 java.lang.Thread 를 사용하여 로그 파일에 작성할 것이다. NHRT가 더 많은 작업을 기다리고 가비지 컬렉터가 실행하지 않을 때 작업을 끝낸다. 전통적인 힙 기반 자바와 NHRT의 Non-Heap 환경 간 인터페이스를 소개했을 뿐이므로 디자인 결정은 많은 함축이 가능하다. 나중에 보겠지만, 이 인터페이스를 처리할 때 신중해야 한다.
그림 7은 이 아키텍처의 고급 다이어그램이다.
|
|
|
이제 NHRT 애플리케이션이 어떤 일을 하는지 알게 되었으므로, 시스템이 어떤 메모리 영역으로 들어가야 하는지 파악해야 한다.
우리의 디자인에 한정과 영구 메모리를 적용하기 잔에, 이들의 작동 방법에 대해 이해해야 한다.
영구 메모리에서 생성된 객체들은 결코 지워지지 않으며 애플리케이션 수명 기간 동안 살아있다. 객체 사용을 끝냈더라도 공간을 사용하고 복구될 수 없다. 이는 확실히 영구에서 객체를 트래킹 하고 시간이 흐르면서 객체 생성을 막아야 하는 프로그래머에게는 부담이 된다. 영구 메모리 누수는 RT 자바 애플리케이션의 일반적인 에러이다.
한정 메모리에서 생성된 객체들은 자신들이 생성된 범위의 수명 기간 동안 살아있다. 각각의 한정 메모리 영역은 레퍼런스 카운트를 갖고 있다. 쓰레드가 한정 메모리 영역으로 들어가면, 레퍼런스 카운트가 증가하고, 떠나면 레퍼런스 카운트는 줄어든다. 레퍼런스 카운트가 0에 다다르면 그 범위에 있는 객체들은 릴리스 된다. 한정 메모리 영역들은 이들이 생성될 때 지정된 최대 크기를 갖고 있고 이들이 사용되어야 하는 태스크로 조정되어야 한다. RT 애플리케이션의 디자이너들은 범위들을 특정 태스크로 제휴시켜 이들이 효과적으로 조정될 수 있도록 한다. 범위 크기는 고정되고 사전 선언되므로 예견할 수 없는 메모리 양을 사용하는 태스크에는 잘 맞지 않는다.
Non-Heap 메모리에 대해 이해했다면 이제는 이것을 시스템에 적용해 본다.
메모리 관점에서, 감사 시스템은 쉽다. 힙 메모리에 있는 java.lang.Thread 에서 실행된다. 표준 자바 쓰레드를 사용하든 힙 기반 RT 쓰레드를 사용하든, 가비지 컬렉터에 의해 관리되는 메모리에서 스트링 조작과 I/O를 수행하는 것이 합리적이다. 이러한 연산은 놀랍게도 많은 메모리를 사용할 수 있다.
우리 시스템에 있는 나머지 쓰레드는 NHRT이고, 자바 힙을 사용하여 객체를 할당할 수 없다. 우리의 선택은 한정 메모리와 영구 메모리의 결합으로 제한된다.
모든 쓰레드는 쓰레드의 수명 동안 사용된 초기 메모리 영역을 갖고 있다. 우리 디자인에서, NHRT는 장기 실행이기 때문에, 초기 메모리 영역을 위해 선택했던 것이 무엇이든지, 처음 시작 후에 메모리를 계속 사용해서는 안된다. 한정이든 영구이든 상관없이, 메모리는 비워지지 않고 실행되기 때문이다.
현재 메모리 영역은 객체 할당에 의해서만 소비되기 때문에 메모리 관리의 한 가지 접근 방식은 고정된 수의 객체들만 사용하거나 이들을 모두 피하는 것이다. 스택에서 기본 값을 사용함으로써, 현재 메모리 영역을 사용하지 않고 작업을 수행할 수 있다. (이 스택은 함수 인자를 갖고 있는 메모리 섹션이고 메소드에 사용되는 필드이다. 자바 힙과 영구 또는 한정 메모리에서 분리되지만 객체를 가질 수 없다. 오직 기본 값 또는 객체 레퍼런스만 갖는다.)
하지만, 자바 언어와 이것의 클래스 라이브러리들은 객체를 사용하여 목표를 달성하도록 한다. 따라서, 이 예제의 경우 NHRT가 수행해야 하는 연산은 일부 객체들을 만들고 이것이 수행될 때마다 메모리를 사용하는 것이다.
시스템이 많지만 지정되지 않은 시간 동안 플랫 메모리 프로파일을 가져야 하지만 여전히 객체들을 생성하는 이 시나리오에서, 가장 좋은 접근 방식은 영구 메모리에 쓰레드를 시작하고 한정 메모리의 영역을 특정한 유한 태스크에 할당하는 것이다.
쓰레드가 실행될 때, 이것이 태스크를 수행할 때마다, 이것은 범위에 들어가고(사이즈는 이 태스크용으로 정해졌다.), 태스크를 수행한 다음, 범위를 떠나서 소비된 메모리를 릴리스 한다. 보다 강력한 기술을 적용하려면, 여러분이 수행하는 태스크는 한계가 정해져서 한정 메모리의 양을 예견하고 정해야 한다.
다중 쓰레드들 간 범위 공유는 가능하지만 메모리 범위에 대한 single-parent 규칙 때문에 어렵다. 공유 범위를 관리하는 것은 쉽지 않다. 한 범위는 모든 쓰레드가 이를 일단 떠나면 복원된다. 다시 말해서, 범위 사이즈가 조정되어 많은 쓰레드들이 태스크를 동시에 수행할 수 있도록 해야 한다.
일반적으로, 한 번에 한 쓰레드에서 하나의 태스크에 하나의 범위만 고수한다면, NHRT로 개발하는 것이 더 쉽다. 우리 예제에서, 각각의 생산 라인 폴링 쓰레드는 영구 메모리에서 시작되고 ProductionLine 을 쿼리하기 전에 범위가 생성된다. 각각의 우선 순위 풀 쓰레드는 영구에서 시작될 것이고 스택에서 기본 데이터를 사용하여 계산을 수행한다. 각각의 쓰레드는 WorkerConsole 인터페이스(여기에서 객체들이 생성됨)를 사용해야 한다면 범위를 갖게 될 것이다.
한가지 마지막 메모리 문제는 쓰레드들 간 통신하는 방법이다. ProductionLine 폴링 쓰레드는 데이트를 우선 순위 풀로 보내야 하고, 우선 순위 풀에 있는 각 쓰레드는 데이터를 감사 쓰레드로 전달해야 한다.
인자로서 기본 값을 메소드로 전달함으로써 이 문제를 간단히 풀 수 있다. 모든 데이터들이 스택에 있기 때문에, 메모리 영역에는 어떤 문제도 없다.
이 예제 애플리케이션을 위해서 Measurement 클래스를 만들 것이다. 이것의 객체는 측정 데이터를 전달하는데 사용된다. 어떤 메모리 영역에 객체들을 만들어야 할까? NHRT가 액세스 할 수 없기 때문에 자바 힙을 사용할 수 없다. 우리의 아키텍처에서 어떤 범위도 쓰레드들 간 공유되지 않기 때문에 범위를 사용할 수 없다.
힙과 범위를 빼면, 영구 메모리가 남는다. 영구 메모리는 복구되지 않기 때문에 Measurement 객체들을 마음대로 만들 수 없다. 해답은 영구에서 Measurement 객체들을 만들고 이를 재사용 하는 것이다.
MeasurementManager로 측정 객체 풀링하기
재사용 가능한 Measurement 인스턴스를 얻어서 리턴하는 정적 메소드를 사용하여 MeasurementManager 클래스를 만든다. 자바 SE 프로그래머로서, LinkedList 또는 Queue 클래스를 사용하여 데이터 스토어를 제공하여 측정을 보유하고 싶은 유혹을 받는다. 하지만, 두 가지 이유로 그럴 수 없다. 첫 번째 이유는 대부분의 SE 컬렉션 클래스들은 뒤에서 객체들을 만들어서 데이터 구조를 유지하고 이러한 방식으로 객체들을 만들면 영구 메모리 누수로 이어진다. 두 번째 이유는 보다 미묘하다. 힙과 Non-Heap 정황에서 실행하는 쓰레드들간 차이를 메우면서, 대부분의 멀티쓰레디드 애플리케이션의 경우와 마찬가지로 우리가 어떤 데이터 구조를 사용하든지 간에 배타적인 액세스를 보장하기 위해 잠금을 사용해야 한다. NHRT와 힙 기반 쓰레드들 간 잠금의 공유는 가비지 컬렉터가 우선순위 역전 보호의 부작용으로 NHRT를 선점하게 한다; " Real-time Java, Part 3: 쓰레딩과 동기화 ".
NHRT와 NHRT에서 제공되는 힙 기반 쓰레드들간 데이터 공유 솔루션은 WaitFreeQueue 클래스이다. NHRT가 차단의 위험 없이 (클래스에 기반하여) 데이터를 읽거나 쓰는 요청을 할 수 있는 wait free 사이드를 갖고 있는 큐이다. 이 큐의 다른 사이드는 전통적인 자바 동기화를 사용하고 힙 쓰레드에 의해 사용된다. Non-Heap과 힙 기반 환경들 간 잠금을 공유하는 것을 피함으로써, 데이터를 안전하게 교환할 수 있다.
MeasurementManager 는 NHRT 측정과 힙 기반 감사 쓰레드에 의해 사용되어 측정을 리턴한다. 따라서, WaitFreeReadQueue 를 사용하여 인터페이스를 관리한다. WaitFreeQueue 의 애프리 사이드는 싱글 쓰레드로 설계된다. WaitFreeReadQueue 는 멀티 라이터, 싱글 리더 애플리케이션으로 디자인 된다. 우리는 멀티 리더, 싱글 라이터 애플리케이션을 사용하여 동기화를 추가하여 단 하나의 NHRT가 한 번에 하나의 측정을 요청하도록 한다. 이는 부가적인 동기화를 추가함으로써 WaitFreeQueue 를 사용하는 목적을 좌절시킨다. 하지만 read() 메소드로의 액세스를 제어하는 모니터는 NHRT들 간 공유되기 때문에 힙과 Non-Heap 정황 간 위험한 잠금 공유는 발생하지 않는다.
NHRT 애플리케이션을 개발할 때 또 다른 중요한 문제가 생겼다. Non-Heap 환경에서 기존의 자바 코드 부분을 재사용하기가 훨씬 더 어려워진다. 여러분도 보았듯이, 각 객체가 할당되는 장소와 메모리 누수를 피하는 방법을 신중히 생각해야 한다. 자바와 객체 지향 프로그래밍의 장점들 중 하나는 Non-Heap 정황에서는 단점이 될 수 있다. 더 이상 메모리 사용을 예견하고 관리할 수 없기 때문이다.
메모리 모델을 설계했으니, 그림 8은 메모리 영역이 표시된 업데이트 된 시스템 다이어그램이다.
그림 8. 메모리 영역이 표시 된 고급 아키텍처 다이어그램

적절한 쓰레드 우선 순위를 선택하는 것은 표준 자바 코드 보다 WebSphere Real Time을 사용하여 개발할 때 더 중요하다. 선택이 잘못되면 가비지 컬렉터가 NHRT를 선점하거나 시스템의 일부는 CPU 부족 현상을 겪게 된다.
" Real-time Java, Part 3: 쓰레딩과 동기화 "에서는 쓰레드 우선 순위의 상세를 설명하고 있다. 우리 예제 시스템에서, 우선 순위를 설정하는 이유는 다음과 같다.
- 폴링 쓰레드에 최대 우선 순위를 주어서 측정을 잃을 위험 부담을 최소화한다.
- 가비지 컬렉터에 의해 인터럽트 되는 풀 쓰레드의 우선 순위를 피한다.
이를 위해서, 폴링 쓰레드의 우선 순위를 38로 설정하고(가장 높은 RT 우선 순위) 우선 순위 구분 풀 쓰레드의 우선 순위를 37로 설정한다. 감사 쓰레드는 표준 우선 순위(5)를 가진 정상 Java SE 쓰레드이기 때문에 이것의 우선 선위는 NHRT 보다 훨씬 낮다.
이러한 설정은 가비지 컬렉터의 쓰레드 우선 순위가 NHRT 보다 훨씬 낮은 감사 쓰레드 위에 있다는 것을 의미한다.
지금까지, 애플리케이션의 스테디(steady) 측면만 보았다. 다시 말해서, 애플리케이션이 실행된 후 작동에 대해서만 보았다. 이것이 처음 어떻게 시작되는지는 고려하지 않았다. WebSphere Real Time 애플리케이션은 표준 자바 애플리케이션처럼 시작한다. 자바 힙의 java.lang.Thread 에서 실행한다. 여기서부터, 다른 메모리 영역에서 여러 쓰레드 유형들을 시작해야 한다.
우리의 애플리케이션에서, 모든 부트스트래핑은 MonitoringSystemImpl 클래스의 startMonitoring() 메소드에서 수행되는데, 힙 메모리에서 실행되는 java.lang.Thread 에 의해 호출된다.
우리의 부트스트래핑 태스크는 다음과 같다.
- 한 개 이상의 폴링 쓰레드를 영구 메모리에서 생성한다.
- 영구 메모리에서 생성 및 실행되는 각각의 풀 쓰레드를 갖고 있는 한 개 이상의 쓰레드 풀 객체를 영구 메모리에서 생성한다.
- 힙에서 실행하면서, 영구 메모리에 감사 쓰레드 객체를 생성한다.
ImmortalMemory.newInstance() 메소드를 사용하는 java.lang.Thread 에서 영구 메모리에서 객체를 만들 수 있다. 약간의 생성자 인자를 가진 클래스의 경우 또는 같은 클래스의 많은 객체를 생성하고 있다면, 생성자가 많은 인자들을 갖고 있는 클래스가 되어버린다.
java.lang.Thread 와는 달리, RealtimeThread 는 영구 메모리로 들어가서 일부 작업을 수행하거나( ImmortalMemory.enter()) 에 Runnable 을 구현하는 객체를 제공함), 그 쓰레드용 초기 메모리 영역으로서 영구 메모리를 제공한다. 이러한 방식의 장점은 표준 자바 코드를 작성할 수 있고 모든 new 연산은 영구 메모리에 객체를 만들 수 있다. 단점은 힙 기반 java.lang.Thread 에서 영구 메모리에서 실행되는 RT 쓰레드로 이동하여 코드가 어지럽혀진다는 점이다.
예제 코드에서, 우리는 유틸리티 메소드인 -- Bootstrapper.runInArea -- 를 작성했는데, 이것은 MemoryArea 와 Runnable 객체를 취한다. 내부적으로, 이것은 제공된 메모리 영역에 단기간 존재하는 RealtimeThread 를 시작하고 Runnable 를 실행한다. 이는 부트스트래핑에 더 알맞은 방식들 중 하나이다.
어떤 노력을 하든지 간에, 이러한 부트스트래핑 코드를 깔끔하게 유지하는 것은 어렵고 읽기는 쉽다. 메모리 영역과 쓰레드 유형들 간 일관된 홉핑(hopping)은 아키텍처 다이어그램을 참조하지 않고는 코드를 읽는 사람들에게 설명하기 어렵고 숙련된 자바 프로그래머를 위협하는 구조를 만들게 된다는 것이다. 최상의 방법은 이것의 배경을 설명하는 것이다.
디자인의 기본 개념을 설명했으니, 완성된 애플리케이션을 살펴보도록 하자.
디자인 구현은 소스 코드를 다운로드 하여 참조하라. 소스를 검사하고 실행 가능한 코드로서 구현된 것의 개념을 파악하라.
모니터링 시스템의 구현 외에도, 모형 생산 라인과 작업자 콘솔을 제공하여 모니터링 시스템이 테스트 될 수 있도록 했다. 항아리는 Gaussian에서 생산되는데, 이는 가끔 넘치거나 부족하다.
이 데모는 시스템에 어떤 일이 발생하는지를 나타내는 메시지와 함께 콘솔 애플리케이션으로서 실행된다.
본 데모 패키지에는 다음과 같은 디렉토리와 파일이 포함된다.
- src -- 데모용 자바 소스.
- build.sh -- 데모 구현을 위한 bash 쉘 스크립트.
- MANIFEST.MF -- 데모 JAR 파일용 선언 파일.
이 데모를 구현하려면, 알맞은 디렉토리에 압축을 풀고, SweetFactory 디렉토리로 변경한 다음, build.sh를 실행한다. WebSphere Real Time에서 제공되는 jar, javac, jxeinajar가 있어야 하고, 이는 build.sh 스크립트가 작동하도록 PATH 에서 사용할 수 있다.
build.sh 스크립트는 여러 연산을 수행한다.
- bin 디렉토리를 생성하여 클래스를 저장한다.
- javac 를 사용하여 자바 소스를 구현한다.
- sweetfactory.jar라고 하는 JAR 실행 파일을 구현한다.
- jxeinajar 를 사용하여 sweetfactory.jar를 AOT-컴파일 한다.
이 빌드 스크립트를 실행하면 다음과 같은 결과가 나온다.
Listing 1. 스크립트 아웃풋
build.sh 스크립트를 실행하면 sweetfactory.jar가 만들어지는데, 이는 Sweet Factory 데모의 AOT-컴파일 버전이다.
Sweet Factory 데모를 구현했으니 실행해 보자. 이 데모는 WebSphere Real Time v1.0의 SR1릴리스로 구현 및 테스트 되었고, 여러분은 SR1 또는 이후 버전으로 실행하기 바란다.
Listing 2. Sweet Factory 데모
아웃풋에서, 기본적으로 이 데모를 볼 수 있고, 도착하는 각 jar 사이에 20초 지연을 설정하여 세 개의 생산 라인을 시작한다.
-Xnojit 옵션을 Java VM에 전달하여 AOT 버전의 애플리케이션을 사용한다.
데모가 실행되면서 다양한 항아리들이 넘치거나 덜 채워지고, 메시지가 콘솔에 프린트 되고, 타임스탬프가 미리 찍힌다. 끝에 가서, 각 생산 라인에 얼마나 많은 항아리들이 소실되었는지를 보여주는 테이블이 프린트 된다.
시스템에 얼마나 많은 부하가 걸렸는지를 평가하는 통계도 있다. 최소 큐 깊이는 측정 객체 풀이 얼마나 얕은지를 보여준다. 풀이 비워지면, 폴링 쓰레드가 인커밍 측정을 저장할 곳이 없기 때문에 항아리를 잃게 된다.
감사의 최대 인커밍 큐 깊이는 한 번에 감사 쓰레드에 의해 처리되기 위해 큐에서 얼마나 많은 측정 객체들이 기다려야 하는지를 나타낸다. 그 숫자가 크다면, 감사 로거는 충분한 작동 시간을 얻지 못하고 큐가 구현되어야 한다는 것을 시사한다.
기본적으로, 이 데모는 Opteron 하드웨어의 기능으로도 충분히 잘 실행된다. 항아리를 잃을 위험이 없다. 하지만, 이 데모는 생산 라인의 수를 늘리고 도착하는 항아리들 간 시간을 줄이기 위해 매개변수화 될 수 있다.
매개변수를 변경함으로써, 머신 작동을 변경할 수 있고, 워크로드를 충분히 높인다면 항아리를 잃게 될 것이다.
이 데모에는 세 개의 인자들이 있다. 초당 런타임, 생산 라인의 수, 밀리초로 도착하는 항아리들 간 간격.
워크로드를 높이기 전에, 생산 라인의 수를 늘리면 데모 내에서 실행되는 쓰레드의 수가 증가한다. 각 NHRT에는 범위가 있기 때문에, 쓰레드의 수를 늘리면 총 메모리 공간이 늘어나다가 결국 소진하게 된다.
java -Xrealtime -verbose:sizes -version 을 실행하면 기본적인 총 메모리 공간이 8MB라는 것을 알 수 있다.
Listing 3. java -Xrealtime -verbose:sizes -version
우리가 각 태스크에 할당했던 한정 메모리의 양을 알고 있고(100KB per NHRT), 각 생산 라인에 11 NHRT를 만들었다. 이를 사용하여 보다 적극적인 워크로드를 실행하기 위해 -Xgc:scopedMemoryMaximumSize 로 사용해야 하는 총 한정 메모리의 양을 측정할 수 있다.
예를 들어, 10 밀리초 간격으로 50 개의 생산 라인을 실행하기 위해서는, 최소한 55MB의 한정 메모리가 필요하다.
생산 라인의 수를 충분히 늘리면(10 밀리초 간격으로 최대 70까지 가능하다.), 데모는 항아리를 잃기 시작한다. 이 같은 일이 발생하면, 다음과 같은 메시지가 콘솔에 프린트 된다.
첫 번째 메시지는 풀에서 측정 객체를 얻지 못할 경우 폴링 쓰레드에서 온 것이다. 두 번째는 폴링 쓰레드가 평가 객체를 받았을 때 얼마나 많은 항아리들을 소실했는지를 나타낸다.
이러한 상황에서, 대부분의 CPU 시간은 인커밍 측정을 핸들하는데 보내진다. 부하가 늘어날수록 Metronome을 실행할 충분한 시간이 없고 감사 로그를 작성한다. 이 평가는 감사 시스템 앞에 있는 큐에 구현되면서 측정 풀을 소진한다. 측정이 다 되고 폴링 쓰레드가 그 다음의 순서를 기다릴 때에만 로깅 쓰레드는 로그를 작성하고 측정을 풀에 리턴 하는데 필요한 CPU 시간을 얻는다.
WebSphere Real Time을 사용한 지 1년이 지나니 RT 애플리케이션을 활용하는 팁과 트릭의 윤곽이 잡혔다. 본 섹션에서는 몇 가지 유용한 방법을 소개한다.
Non-Heap 메모리에서 개발할 때, 여러분이 어떤 메모리 영역에 있고 어떤 유형의 쓰레드를 각 코드 라인에서 실행하는지 생각해야 한다.
불법 할당을 수행하거나, java.lang.Thread 에서 메모리 영역에 들어갈 경우 버그 위험이 높아지고
코드에 assert() 문을 두어서 인자에 대한 정상성 체크를 하는 것은 Java SE에는 좋은 습관이고, RT 자바 코드에서도 쓰레드 정황과 메모리 영역을 선언하는 것이 좋다.
샘플 Sweet Factory 애플리케이션에는 전용 ContextChecker 클래스가 있는데, 이것은 checkContext 메소드와 상수를 제공하여 다른 정황을 나타낸다.
표준 자바 코드에서, 내부 메모리 환경 때문에 에러 핸들링은 코드의 또 다른 문제 영역이 되었다. Non-Heap RT 자바에서, 에러 핸들링은 큰 문제가 될 수 있다.
앞서 언급했듯이, NHRT에서 수행하게 될 대부분의 태스크는 메모리를 사용하고, 그러한 태스크를 위해 범위나 풀 객체들의 사용을 조정해야 한다.
에러를 만나게 되면, 에러 메시지를 갑자기 프린트 하는 것 같은 단순한 작동은 문제를 일으킬 수 있다. 이 연산을 수행할 메모리가 없을 수 있기 때문이다. 한 가지 방법은 이러한 모든 상황을 위한 충분한 오버헤드를 제공하여 충돌 전에 디버깅 라인을 프린트 하는 것이다. 하지만 이는 비현실적이다.
우리가 찾은 최상의 방법은 Runnable 을 확장하고 메소드를 제공하여 오류에 대한 데이터를 제공하는 각 에러 조건에 대한 클래스를 만드는 것이다. (여러분은 어떤 일이 발생했는지에 대한 충분한 정보를 얻게 된다.) 클래스의 인스턴스를 미리 만들어서 메모리를 소비하지 않도록 한다. 에러 핸들링 연산을 수행할 수 있는 한정 메모리 영역을 예비해 둔다.
사전 할당 된 Runnable 객체와 개별 범위에서, 에러가 발생한 정황에서 메모리를 사용하지 않고 문제를 보고할 수 있어야 한다. 이는 더 많은 객체들을 만드는 것이 불가능 할 때 던져지는 OutOfMemoryError 같은 상황에 유용하다.
ProductionLinePoller 클래스에서 Sweet Factory 데모를 통해 기술을 설명했고, errorReportingRunnable 이 풀에서 Measurement 를 내보내지 못할 경우 사용되도록 했다.
WebSphere Real Time 플랫폼에서 RT 자바 애플리케이션들을 개발 및 전개하여 매우 결정적인 특성에 부합하도록 하는 방법을 설명했다. NHRT 프로그래밍과 Non-Heap 메모리는 일반 힙 기반 애플리케이션을 작성하는 것 보다 더 많은 일을 수행한다. Sweet Factory 데모를 생각해 보자. 비슷한 함수를 힙 환경에 작성했다면, 매우 간단했을 것이다. Java SE 표준 라이브러리는 쓰레드 풀과 컬렉션 클래스를 포함하여 우리가 필요로 했던 대부분의 기능을 제공한다.
NHRT로 작업할 때 가장 큰 문제는 새로운 기술이라는 것 외에도 디자인 패턴을 포함하여 Java SE에서 방법을 배워야 한다는 점이 쉽지가 않고 메모리 누수를 야기한다는 점이다.
다행히도, NHRT에서 생성자를 호출하지 않고 WebSphere Real Time에서 많은 소프트 RT 목표를 달성할 수 있다. Metronome 가비지 컬렉터의 성능으로 밀리초 정밀도로 예견 가능성을 실현할 수 있다. 하지만, 반응성을 높이고 싶을 경우, WebSphere Real Time의 Non-Heap 기능도 도움이 된다.



