JAVA NIO
NIO : New Input Output
이라는 이름으로 Java 4부터 기본 패키지에 포함된 API.
[ NIO 등장 배경 ]
JAVA는 느리다!
이 말은 프로그래밍을 배운 사람이라면 한번 쯤은 들어본 말이다.
이유는 크게 4개 정도로 볼 수 있다
- C/C++ 처럼 Memory를 직접 관리할 수 없다.
- System Call을 직접 사용할 수 없다.
- JVM이라는 추상적 공간을 띄운 뒤 그 안에서 실행된다.
- GC(Garbage Collection)관리로 메모리가 요구된다.
여기서! Java I/O가 특별히 성능 면에서 부족한 면이 많은데 이와 연관성이 깊은 이유는 `1. Memory 직접 접근/관리 불가` 와 2. `System Call 직접 사용 불가` 이다.
이를 개선하고자 새로 만들어진 API가 NIO
패키지인 것이다.
기존 I/O의 비효율성
Java I/O Process
- Application Process가 Kernel에
파일 Read 명령
을 전달한다. - Kernel에서 System Call을 통해 Disk로부터 File 데이터를 가져와
Kernel Buffer에 넣는다
. (DMA 관여) - Kernel Buffer에 모두 넣어지면 이를
다시 Process Buffer에 복사
한다. - Process는 해당 Buffer 내용을 가지고 데이터처리를 시작한다.
이 프로세스를 보면, 왜 자바의 I/O가 특히 느린지를 알 수 있다.
Kernel Buffer -> Application Buffer 이 부분, 프로세스가 커널의 Buffer를 직접 접근할 수 없어 굳이 한번 더 복사를 해야하는 한계가 존재한다.
이를 해결할 수 있다면 어떨까.
커널 영역의 Buffer를 직접 접근할 수 있다면 굳이 Buffer 복사를 위해 CPU 자원, GC 관리자원을 쓸 이유가 없다.
이를 고려하여 새로이 만든 I/O 패키지가 NIO
인 것이다.
[ IO vs NIO ]
비교
구분 | IO | NIO |
---|---|---|
입출력 방식 | Steam | Channel |
버퍼 방식 | Non-Buffer | Buffer Oriented |
비동기 지원 | 지원 안함 | 지원 |
블로킹 방식 | Only Blocking | Both Blocking / Non-Blocking |
JAVA IO Communication
JAVA NIO Communication
Stream vs Channel
- Stream
- Stream은 InputStream과 OutputStream으로 구분되어 있어 데이터 Read/Write 할 시 각각 Stream을 만들어주어야 함
- Channel
- Channel은 양방향 입출력 가능. 하나의 Channel로 Read/Write 가능
Non-Buffer vs Buffer
- Non-Buffer
- IO는 OutputStream이 1Byte를 쓰면 InputStream 또한 1Byte를 읽는다.
- Stream에서 읽은 데이터를 즉시 처리하므로 Stream에서 입력된 전체 데이터를 별도로 저장하지 않으면 데이터 처리에 제약이 발생
- Buffer
- NIO는 읽은 데이터를 무조건 Buffer에 저장.
- 데이터 처리에 자유로움
- CPU 과부하를 막을 수 있다.
- CPU System Call은 데이터를 한 번 옮길 때마다 자원이 소비된다.
- 이 말은 즉, 글자 1개로 된 객체를 옮기든 글자 1000개로 된 객체를 옮기든 1번의 System Call을 부른다는 것.
- 그렇다면 우리는 파일을 읽을 때 어떤 방식으로 System Call을 사용해야 하겠는가?
Blocking vs Non-Blocking
- Blocking
- IO의 Stream 방식은 Blocking만을 지원한다.
- Thread가 Read 혹은 Write 명령어를 실행하는 동안 Blocking되며, 해당 명령어가 실행되는 동안 Thread는 다른 동작이 불가능하다.
- I/O 작업시간이 길어질수록 해당 Thread를 그 시간만큼 못쓴다는 것.
Socket 통신할 시, I/O 전용 Thread로 지정할 수 있다는(다른 작업에 방해받지 않는다는) 장점이 존재
- Non-Blocking
- NIO Channel 방식은 Blocking / Non-Blocking 모두 지원 가능하다.
- Non-Blocking은 I/O 작업 Ready가 완료된 Channel만 선택하여 Thread가 처리하므로 Blocking 되지 않을 수 있다.
과도한 Thread 생성을 피하고 재사용성이 증가한다는 장점이 존재
- 여기서 SELECTOR라는 멀티플렉서 개념이 등장한다.
[ NIO 특성 ]
NIO의 Keyword는 총 3가지
- Buffer
- Java의 Pointer Buffer(nio 패키지에서 제공하는 Buffer 클래스)
- Channel
- read / write 모두 가능한 양방향식 입출력 Class
- Native IO, Scatter/Gather 모델 형식으로 효율적인 I/O 처리(System Call 줄이기, 모아서 처리하기)
- Selector
- Network 통신 효율을 높이기 위한 Multiplexer
- 하나의 Thread로 여러 Connection을 다룰 수 있도록 하는 Reactor 패턴 구현체
- Selector 사용으로 Thread 과부하 및 성능 저하를 막을 수 있다.
[ NIO 관련 주목해야 할 OS 기술 ]
Scatter / Gather
위에서 언급한 것과 같이 객체 한 개를 옮기는데 System Call 한 번이 사용된다.
System Call은 CPU 자원을 사용하는 것과 같은 일이므로 마구잡이로 사용할 것이 아니다(SW 성능이 크게 좌지우지 되는 요건)
Scatter / Gather 모델방식을 사용하여 Application에서 사용하는 Buffer 목록을 한 번에 넘겨(System Call 한 번 만으로) 자원소모를 줄이는 방식이다.
Virtual Memory
OS 이론수업에서 빼먹을 수 없는 가상메모리.
주로 가상메모리 이야기를 할 때 언급되는 장점은 실제 사용되는 Page만을 메모리에 올려 실제 메모리보다 더 큰 공간을 사용할 수 있다
이지만, 이 뿐만 아니라 다른 장점이 존재한다.
바로
여러 개의 가상 주소가 하나의 물리메모리 주소를 참조할 수 있다
라는 장점을 가진다.
이게 뭘 의미하냐면, 하나의 물리메모리 주소를 사용자측 가상메모리와 Kernel측 가상메모리에서 공유할 수 있다
는 것이다.
이 장점을 활용하면 위에서 I/O의 문제점으로 언급했던 불필요한 복사작업을 생략할 수 있다!!
결론
I/O를 개선시키고자 만든 NIO 라고는 하지만, 그렇다고 NIO만 쓰는 것이 좋은 것만이라고 할 수는 없다.
Stream 또한 보통 BufferedStream으로 Wrapping해서 쓰고 있어 Buffer문제에서는 다소 자유로워졌고, Non-Blocking만이 답은 아니기 때문이다.
Client 수가 정해져 있고, 한 번 I/O가 시작되면 많은 수의 통신이 필요한 작업의 경우 Stream 전용 Thread를 지정하여 안정성있는 통신을 도모할 수 있다.
반면, 짧은 통신을 주로 이루며 Client 수가 넓은 폭으로 변동하는 서비스의 경우 Non-Blocking 형식의 NIO 통신을 사용하여 Thread를 효과적으로 사용해야 할 것이다.
해당 글을 위한 내용 출처