얼마 전 Java 의 Thread 클래스 소스 코드를 들여다보던 중 native
키워드가 붙어 있는 메소드들을 발견했습니다. 특이한 점이 native
키워드를 포함하고 있는 메소드들은 그 구현체를 찾아볼 수 없었는데 도대체 이 메소드들은 어떻게 작동하는 것인지에 대한 궁금증이 생겼습니다. 따라서 이번 포스팅에서는 Java 의 Native Method 에 대해 알아 보도록 하겠습니다.
JNI
위에서 소개한 native
메소드를 알아 보기 전에 먼저 Java Native Interface(JNI)에 대해 알아야 합니다.
JNI 는 Java 프로그램이 다른 언어로 작성된 프로그램과 상호 작용할 수 있게 해주는 인터페이스로 C 와 C++ 로 작성된 프로그램과의 상호작용을 위해 사용됩니다. 즉 JNI 는 C, C++ 처럼 인터프리터 없이 OS 가 바로 읽을 수 있는 형태의 네이티브 코드를 JVM 이 호출할 수 있게 하는 인터페이스입니다. 위키피디아의 JNI 내용에 따르면 JNI 는 다음과 같은 상황에서 유용하게 사용될 수 있다고 합니다.
- 표준 Java 클래스 라이브러리가 플랫폼별 기능이나 프로그램 라이브러리를 지원하지 않는 경우와 같이 애플리케이션을 Java 프로그래밍 언어로 완전히 작성할 수 없는 상황을 처리하는 기본 메소드를 작성할 수 있다.
- Java 애플리케이션에 액세스할 수 있도록 기존 애플리케이션(다른 프로그래밍 언어로 작성된)을 수정하는데 사용한다.
즉 JNI 는 일반적인 Java 코드를 작성하는 것과 동일한 방식으로 외부 함수를 손쉽게 사용할 수 있게끔 도와 주는 역할을 합니다.
Native Method
위에서 설명한 JNI 를 통해 Native 메소드는 Java 언어 외부에서 구현된 메소드를 Java 코드에서 호출하는 메소드를 의미합니다. native
키워드를 사용하여 선언되며 실제 구현은 Java가 아닌 C/C++ 로 작성됩니다. JVM 영역을 벗어나 운영 체제의 기능이나 기존의 네이티브 라이브러리에 접근할 수 있게 해줍니다.
예제
Java 에서 실제로 사용되고 있는 Native 메소드는 어떤 것이 있는지 Thread 클래스를 통해 알아 보도록 하겠습니다. Thread 클래스에는 다양한 native
메소드가 존재하지만 그 중 start()
메소드를 살펴 보면 다음과 같습니다.
public class Thread implements Runnable {
...
public synchronized void start() {
if (threadStatus != 0)
throw new IllegalThreadStateException();
group.add(this);
boolean started = false;
try {
start0();
started = true;
} finally {
try {
if (!started) {
group.threadStartFailed(this);
}
} catch (Throwable ignore) {
}
}
}
private native void start0();
...
}
start()
메소드는 실제로는 native
메소드인 start0()
메소드를 호출하는 것으로 구현되어 있습니다. JDK 의 구 스레드 모델은 KLT(kernel-level thread)와 ULT(user-level thread)를 1:1 매핑하여 사용하는데 이를 위해서는 운영 체제의 네이티브 API 를 호출해야 합니다. 따라서 start0()
메소드는 OS 가 바로 읽을 수 있는 형태의 native code 를 호출하기 위해 native
메소드를 사용하고 있는 예가 됩니다. 그렇다면 개발자가 직접 native
메소드를 정의해서 사용해봄으로써 JNI 와 natvie
메소드에 대해 좀 더 쉽게 이해할 수 있도록 실습을 해 보도록 하겠습니다.
실습
위에서 작성한 내용들을 기반으로 native
메소드를 직접 정의하고 실행해보는 실습을 진행하도록 하겠습니다.
먼저 외부 라이브러리를 읽어 오고 JNI 를 통해서 메소드를 실행하기 위한 클래스를 아래와 같이 작성합니다.
public class HelloJNI {
static {
System.loadLibrary("hello");
}
private native void sayHello();
public static void main(String[] args) {
new HelloJNI().sayHello();
}
}
- static block 은 클래스 로더에 의해 로드될 때 한 번만 실행되는 코드 블록으로 네이티브 라이브러리를 로드하기 위한 코드를 작성합니다.
native
메소드인sayHello()
를 정의합니다. 해당 메소드는 JNI 를 사용하겠다는 예약어 입니다.main()
메소드에서HelloJNI
인스턴스를 생성하여sayHello()
를 호출합니다.
이제 준비는 끝났습니다. 실제로 native
메소드를 구현하기 위해 작성된 java 파일을 컴파일 하고 sayHello()
를 구현하기 위한 헤더 파일을 만듭니다.
javac HelloJNI
javah io.github.ones1kk.HelloJNI
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class io_github_ones1kk_HelloJNI */
#ifndef _Included_io_github_ones1kk_HelloJNI
#define _Included_io_github_ones1kk_HelloJNI
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: io_github_ones1kk_HelloJNI
* Method: sayHello
* Signature: ()V
*/
JNIEXPORT void JNICALL Java_io_github_ones1kk_HelloJNI_sayHello
(JNIEnv *, jobject);
#ifdef __cplusplus
}
#endif
#endif
생성된 헤더 파일을 포함하여 native
메소드를 구현합니다.
#include <jni.h>
#include "io_github_ones1kk_HelloJNI.h"
JNIEXPORT void JNICALL Java_io_github_ones1kk_HelloJNI_sayHello(JNIEnv *env, jobject obj) {
printf("Hello");
}
작성된 코드를 공유 라이브러리로 등록하기 위해 명령어를 입력합니다.
gcc -arch x86_64 -I$JAVA_HOME/include -I$JAVA_HOME/include/darwin -I"${헤더파일경로}" -shared -m64 ${경로}/${파일명}.c -o ${라이브러리명}.dylib
-I$JAVA_HOME/include
: JAVA_HOME 환경 변수에 설정된 JDK 디렉토리의 include 디렉토리를 포함하도록 지정합니다. JNI 헤더 파일(jni.h)을 찾는 데 필요합니다.-shared
: 공유 라이브러리를 생성하고 해당 파일이 실행 파일이 아닌 공유 라이브러리임을 나타냅니다.-o ${라이브러리명}.dylib
: Output File 에 대한 옵션으로 생성될 동적 라이브러리의 이름과 경로를 지정합니다. Dynamic Library Design Guidelines 규약에 따라lib${라이브러리명}.dylib
의 prefix, suffix 를 가집니다.-arch x86_64
: 애플 실리콘 Mac 으로 실습이 진행 되었기 때문에 타겟 아키텍처를 64비트(x86_64)로 지정합니다.
등록된 공유 라이브러리의 경로를 환경변수에 주입하여 main
메소드를 실행시킵니다.
java -Djava.library.path=${dylib파일경로} -classpath ${classpath} ${패키지경로}.${자바파일명}
구현한 native
메소드가 정상적으로 실행되는 것을 확인할 수 있습니다. 즉 이렇게 JNI 를 사용하여 별도의 인터프리터 없이 C 로 작성된 코드를 실행하는 실습을 성공적으로 마무리했습니다.
오탈자 및 오류 내용을 댓글 또는 메일로 알려주시면, 검토 후 조치하겠습니다.
'Language > Java' 카테고리의 다른 글
[Java] Thread Pool (0) | 2024.07.18 |
---|---|
[Java] Thread (1) | 2024.06.30 |
[Java] 동기화 (0) | 2024.03.13 |
[Java] 데이터 병렬 처리(Java 8) (0) | 2024.01.08 |
[Java] 데이터 병렬 처리(Java 5, 7) (1) | 2023.12.31 |