2018년 11월 12일 월요일

Java의 NIO (New I/O)

IO : NIO = Stream : Channel

Java의 기존 I/O는 느렸다. 다행히 어렵진 않았다. Buffered 계열의 클래스를 이용해 쉽게 I/O 작업을 할 수 있었다.
 - FileReader / FileWriter
 - PrintReader / PrintWriter

NIO 이전의 I/O는 Stream 기반으로 input/output 스트림을 구분하는 방식이다. 읽기를 수행할 땐 InputStream을 사용해야 하고, 쓰기를 수행할 땐 OutputStream로 구분해서 사용해야 한다. 그리고 읽기/쓰기가 끝나야 return 되는 blocking 방식이다.

기존의 I/O 방식이 느렸던 까닭은 커널 버퍼에 직접 접근하는 Direct Buffer를 핸들링할 수 없었기 때문이다. 이로 인해 커널에서 JVM 내부로 Buffer를 복사할 때의 CPU 소모, 복사한 Buffer에 대한 GC 등의 오버헤드가 발생하였다.

JDK 1.4부터 NIO가 생겼다. NIO는 New I/O의 줄임말이다. (Non-blocking I/O를 뜻하는 것이 아니다.) 기존의 I/O는 Blocking방식으로, 해당 블럭이 끝나기 전에는 아무 것도 수행할 수 없었다. 왜 1.4 이전엔 이런게 없었을까? 멀티 플랫폼을 지향하는 자바 특성상 각 OS의 System Call이나 커널에 맞춰 Non-blocking I/O를 지원하기 어려웠을 것이다.

NIO가 되면서 커널의 Direct Buffer에 접근할 수 있는 클래스가 생겼다. Buffer는 시스템 메모리를 직접 사용할 수 있는 클래스로 기본 데이터 타입을 저장할 수 있다. 형태는 배열과 유사하고 제한된 크기에 순서대로 데이터를 저장한다. Buffer에는 read/write 위치에 대한 position, read/write 최대 값인 limit, 그리고 크기에 해당하는 capacity 등의 속성이 있다. 메서드로는 rewind(), flip(), compact(), slice()등이 있다.

Buffer 자체는 추상 클래스이며 이에 대한 Concrete 클래스로
 - ByteBuffer
 - MappedByteBuffer
 - CharBuffer
 - DoubleBuffer
 - FloatBuffer
 - IntBuffer
 - LongBuffer
 - ShortBuffer
가 있다. 이 중 ByteBuffer는 시스템 메모리를 직접 사용하는 Direct Buffer를 만들 수 있는 유일한 Buffer이다.

NIO에서는 기존 I/O 방식과 다르게 Channel이라는 것을 통해 양방향 읽기/쓰기를 동시에 할 수 있게 지원한다. Channel도 Stream과 마찬가지로 데이터가 흘러다니는 통로지만 Stream과 달리 input/output을 구분할 필요가 없다.

Channel은 Buffer를 통해서만 데이터를 읽거나 쓸 수 있다. 또 하나의 특징으로 Channel은 Non-blocking operation도 지원한다. NIO의 일부가 Non-blocking을 지원한다는 것이지 NIO의 모든 동작이 Non-blocking 방식으로 동작하는 것은 아니다. Blocking 모드로 동작하더라도 성능상 유리한 System Call을 활용하기 때문에 기존의 Stream I/O에서 병목을 유발했던 몇가지 레이어를 건너뛸 수 있다. 그리고 NIO에서는 Selector라는 것을 통해 모든 연결을 중앙집중식으로 관리하게 된다. 이벤트를 Selector가 감지하고, 이벤트가 발생한 I/O와 관련된 Channel을 불러와 작업을 수행하는 방식이다.

댓글 없음:

댓글 쓰기