메모리 관리 또는 퍼포먼스 이슈를 뛰어넘기 위해서는 native codes (C/C++)를 써야할 때가 있습니다.
자바에서는 native codes를 Java Native Interface (JNI)를 통해서 실행시킬수 있습니다.

Configuring IntelliJ

IntelliJ 사용자는 먼저 JNIHelper plubin을 설치해야 합니다.
settings > Tools > External Tools > 추가

Name Linux Windows
Program /usr/bin/javah C:\Program Files\Java\jdk1.8.0_91\bin\javah.exe
Parameters -v -jni $FileClass$  
Working Directory $SourcepathEntry$  

Tutorial

JNITutorial.java

System.loadLibrary(“hello”);는 sayHello() native method를 포함하고 있는 native library “hello.so” 또는 “hello.dll”을 불러옵니다.

package anderson.jni.tutorial;

public class JNITutorial {
    static{
        System.loadLibrary("hello");
    }

    private native void sayHello();

    public static void main(String[] args){
        new JNITutorial().sayHello();
    }
}

javac 를 하면 class파일이 생겨나고, javah 를 하면 .h파일이 생성됩니다.

javac HelloJNI.java

javah를 사용할때는 뒤의 .class를 빼야하며, package 사용시 slash (/) 사용이 아닌 dot (.)을 사용합니다.
또한 package 사용시 해당 class가 위치해있는 폴더에서 작업하면 안되며, package root에서 실행을 해야 합니다.
javah실행후 HelloJNI.h 파일이 생성이 됩니다.

javah anderson.jni.tutorial.HelloJNI

Error: Could not find class file for ‘HelloJNI’
javah 사용하다가 위와같은 에러 발생시, 일단 package안에서 돌렸는데.. package root에서 돌린게 아니라, class파일이 위치한 곳에서 바로 돌렸기 때문에 나는 에러일 가능성이 높습니다.

JNITutorial.h

javah로 compile을 하면 PACKAGE_CLASSNAME.h 형식으로 (anderson_jni_tutorial_JNITutorial.h) 만들어 집니다.

/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class anderson_jni_tutorial_JNITutorial */

#ifndef _Included_anderson_jni_tutorial_JNITutorial
#define _Included_anderson_jni_tutorial_JNITutorial
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     anderson_jni_tutorial_JNITutorial
 * Method:    sayHello
 * Signature: ()V
 */
JNIEXPORT void JNICALL Java_anderson_jni_tutorial_JNITutorial_sayHello
  (JNIEnv *, jobject);

#ifdef __cplusplus
}
#endif
#endif

JNIEXPORT void JNICALL Java_anderson_jni_tutorial_JNITutorial_sayHello
Java_{package_and_classname}_{function_name}(JNI arguments) 형식으로 만들어 집니다.
jobject 는 Java의 this에 해당하는 object입니다.

HelloJNI.c

#include <jni.h>
#include <stdio.h>
#include "anderson_jni_tutorial_JNITutorial.h"

// Implementation of native method sayHello() of HelloJNI class
JNIEXPORT void JNICALL Java_anderson_jni_tutorial_JNITutorial_sayHello(JNIEnv *env, jobject thisObj) {
   printf("Hello World!\n");
   return;
}

Compile을 하기 위해서는 jni.h 그리고 jni_md.h 가 필요합니다.

  • /usr/lib/jvm/java-8-oracle/include/jni.h
  • /usr/lib/jvm/java-8-oracle/include/linux/jni_md.h

windows의 경우에는 hello.dll 로 만들면 되고, Linux의 경우는 libhello.so로 만들면 됩니다.
포인트는 리눅스의 경우 앞에 반드시 lib 를 붙여줘야 합니다.
자바 코드안에서는 동일하게 System.loadLibrary(“hello”); 써주면 됩니다.

Linux Example

gcc -I /usr/lib/jvm/java-8-oracle/include/ -I /usr/lib/jvm/java-8-oracle/include/linux/ -shared -fpic -o libhello.so JNITutorial.c

참고로 Windows(MinGW 64)는 다음과 같이 합니다.

gcc -I "C:\Program Files\Java\jdk1.8.0_91\include" -I "C:\Program Files\Java\jdk1.
8.0_91\include\win32" -shared -o hello.dll JNITutorial.c

hello.so가 만들어진 이후 nm을 통해서 shared library안의 모든 symbols들을 확인해 볼 수 있습니다.

$ nm libhello.so 
0000000000201038 B __bss_start
0000000000201038 b completed.7568
                 w __cxa_finalize@@GLIBC_2.2.5
0000000000000600 t deregister_tm_clones
0000000000000690 t __do_global_dtors_aux
0000000000200e08 t __do_global_dtors_aux_fini_array_entry
0000000000201030 d __dso_handle
0000000000200e18 d _DYNAMIC
0000000000201038 D _edata
0000000000201040 B _end
0000000000000720 T _fini
00000000000006d0 t frame_dummy
0000000000200e00 t __frame_dummy_init_array_entry
00000000000007b8 r __FRAME_END__
0000000000201000 d _GLOBAL_OFFSET_TABLE_
                 w __gmon_start__
00000000000005a0 T _init
                 w _ITM_deregisterTMCloneTable
                 w _ITM_registerTMCloneTable
0000000000000700 T Java_anderson_jni_tutorial_JNITutorial_sayHello
0000000000200e10 d __JCR_END__
0000000000200e10 d __JCR_LIST__
                 w _Jv_RegisterClasses
                 U puts@@GLIBC_2.2.5
0000000000000640 t register_tm_clones
0000000000201038 d __TMC_END__

Run!

먼저 Intellij에서 다음과 같이 java Build Path를 추가해줍니다.

-Djava.library.path=share

share라는 디렉토리를 만들고 libhello.so파일을 옮김니다.

실행시키면 다음과 같은 결과를 볼 수 있습니다.

/usr/lib/jvm/java-8-oracle/bin/java -Djava.library.path=share ...
Hello World!

Scala

Hello World via JNI

이때 중요한 점은 package명이 Java에서 만든 package명과 동일해야 합니다.

package anderson.jni.tutorial

class JNITutorial {
  @native def sayHello(): Int;
}

object JNITutorial extends App {

  System.loadLibrary("hello")
  var jni = new JNITutorial
  println(jni.sayHello())
}

실행시키면 다음과 같은 결과가 나옵니다.

/usr/lib/jvm/java-8-oracle/bin/java -Djava.library.path=share ...
13
Hello World!

Spark

binaryFiles 그리고 toArray를 이용해서 Binary Data 를 SPark로 불러 올수 있습니다. 이때 중요한점은 IOUtils이나 따로 직접 불러낼경우, Concurrency Error를 일으키거나, 데이터가 gabbled 될 수 있습니다.

val sPath = cl.getResource("wsq/r/").getPath
val data = sc.binaryFiles(sPath)
val data2 = data.toArray()

Windows Visual Studio

MinGW 64bit

Download MinGW 64

Command Line Prompt 64bit

Windows10의 경우는 시작버튼을 누르고 -> vs 치면은 VS2015 x64 Native Tool Command가 뜹니다. 이걸로 실행을 하던가.. 아니면 다음과 같이 합니다.

cd "C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC"
vcvarsall.bat amd64
cl -I "C:\Program Files\Java\jdk1.8.0_91\include" -I "C:\Program Files\Java\jdk1.8.0_91\include\win32" -LD sample.cpp -Fehello.dll

-LD 다음에 sample.cpp를 넣어야 합니다.
-Fe 다음에는 새로 만들을 dll 파일 이름을 넣어야 합니다.

Error: C++ exception handler used, but unwind semantics are not enabled

#include <iostream> 넣어줄때 발생했으며, 이경우 –EHsc 를 컬파일링 할때 붙여주면 됩니다.

cl -I "C:\Program Files\Java\jdk1.8.0_91\include" -I "C:\Program Files\Java\jdk1.8.0_91\include\win32" --EHsc -LD sample.cpp -Fehello.dll