12. NIO 개요 04. 자바의 새로운 변화

04. 자바의 새로운 변화

기존 IO와 NIO는 어떠한 변화가 있는가?

1. 자바의 포인터 버퍼 도입

가장 핵심적이면서 중요한 변화가 바로 NIO에서 Buffer 클래스를 도입했다는 점이다. 앞서 우리는 C/C++와 달리 자바의 IO가 비효율적인 원인을 살펴봤다. 물리적 메모리(버퍼)를 커널이 제어하느냐 프로세스가 제어하느냐에 따라, 그리고 DMA, 캐시, 가상 메모리 같은 운영체제가 제공해주는 효율적인 IO 기술을 사용하느냐 못하느냐, 쓸데없이 커널 영역에서 프로세스 영역으로의 데이터 복사 과정을 거치지않느냐에 따라서 효율과 비효율이 결정되었다.

JVM은 하나의 프로세스고 JDK 1.4 이전에 사용되는 모든 메모리는 JVM의 힙 영역에서 관리했다. 결국 IO 과정에서 항상 비효율적인 복사 과정을 거치고 블록킹되며 운영체제가 제공해주는 효율적인 기능도 전혀 사용하지 못했다. 하지만 1.4부터 새롭게 도입된 NIO에서는 커널에 의해 관리되는 시스템 메모리를 직접 사용할 수 있는 Buffer 클래스가 도입되었다. (DirectByteBuffer 한정) 이 클래스의 하부 구현은 C로 만들어져 있고 우리는 추상화되어 제공되는 자바 클래스인 Buffer만을 사용하면 된다. 결론적으로 Buffer에서 제공하는 많은 메소드를 이용함으로써 C/C++의 포인터가 자바에서도 생겼다고 볼 수도 있는 것이다.

2. 네이티브 IO 서비스를 제공해주는 채널 도입

기존의 스트림은 단방향이었다. 읽거나 쓰는 한가지만 가능했다. NIO에서는 이 스트림의 향상된 버전이라고 생각할 수 있는 Channel을 도입했다. 채널은 스트림처럼 읽거나 쓰는 단방향에서부터, 읽고 쓰는 양방향 통신이 가능한 세가지 형식이 존재한다. 또한 운영체제에서 제공해주는 다양한 네이티브 IO 서비스들을 이용할수 있게 해준다.

채널은 Buffer 클래스와 함께 작업하도록 만들어져 있다. 채널을 이용해서 시스템 메모리인 버퍼에 직접적으로 데이터를 읽거나 쓸 수 있게 되었다는 것이다. 또한 채널은 앞서 알아봤던 Scatter/Gather를 구현해서 효율적으로 IO를 처리할 수 있게 되었다.

버퍼 클래스와 채널은 실과 바늘로 비요할 수 있다. 버퍼가 시스템 메모리에 로드되면 앞서 배운 운영체제에서 지원하는 다양한 기능들을 채널을 통해 사용하는 것이다. 결국 네이티브 IO 서비스를 이용할 수 있게 해주는 채널이 도입되었다는 것과 이로인해 버퍼 클래스와 함께 작업하는 양방향 통신이 가능해 졌다는 것이 핵심이다.

3. 셀렉터 도입

셀렉터는 네트워크 프로그래밍의 효율을 높이기 위한 것인데 POSA2(Pattern-Oriented Software Architecture, volume 2)에서 소개된 Reactor 패턴의 구현체다. 기존의 자바 네트워크 프로그래밍에서는 한계가 있었다. 클라이언트 하나당 스레드 하나를 생성해서 처리해야 했는데 사용자가 늘어나면 스레드가 많이 생성됨으로 인해 급격한 성능 저하를 가져왔으며 또한 구조적으로 많은 스레드를 생성해야 했기 때문에 메모리 또한 비효율적으로 사용했다.

NIO에서는 네트워크 프로그래밍의 확장성과 유연성, 효율성을 높이기 위해서 버퍼, 채널과 함꼐 사용되는 셀렉터를 이용함으로써 단 한개의 스레드만으로 수천에서 수만명의 동시 사용자를 처리할 수 있는 서버를 만들 수 있게 되었다.

Q. 이제까지 본 것들이 NIO를 공부하기위해 필요한 제반 지식이라 할 수 있는 것들이다.IO의 흐름을 살펴보면서 자바 IO가 왜 느렸는지, 운영체제에서 IO 성능 향상을 위해 사용하는 버퍼, Scatter/Gather, 가상 메모리, 메모리 맵 파일, 파일 락킹에 대해서 공부했다. 이 책은 해당 기술의 배경이 되는 지식과 그 기술의 전반적인 부분을 살펴보고 앞으로 공부할 기술들에 대해 훨씬 더 쉽고 재미있게 접근할 수 있을 것이다.

출처 : 김성박 송지훈 공저, 『 자바 IO & NIO 네트워크 프로그래밍』, 한빛미디어(2004.9.30), 12장 인용