새로운 내용을 공부할 때
새로운 내용의 공부를 시작할 때 용어의 정의를 이해하지 못하거나 정확하게 알지 못한다면 그 용어가 포함된 문장을 이해하지 못합니다.
작은 단어 하나가 내용을 이해하지 못하게 하기 때문에 용어를 정확하게 이해하는 것이 중요합니다.
자바의 신 2권 복습 및 서블릿 쪼끔
목표 D-day : 84 일 리마인트 12회
학습목표
- Block
- Non-Block
- Serializable
- TCP/IP
Block I/O
소켓은 send-buffer
와 recv_buffer
를 가지고 있습니다.
클라이언트는 서버에 전공할 데이터를 send_buffer에 작성을 하면 서버 소켓의 recv_buffer에 전달됩니다.
이때 서버 소켓에 read(soket A)
를 하게 된다면 buffer
에 데이터가 들어올때까지 read()
를 호출한 자바 애플리케이션 쓰레드는 block
상태가 됩니다.
이제 클라이언트 소켓은 send_buffer
가 꽉차서 비워지기 전까지 write(socket s)
을 호출한 쓰레드는 블락이 됩니다.
Non Block I/O
자바 쓰레드 A가 Non Block으로 I/O를 요청할 경우 파일 시스템 인터페이스를 통해 read
시스템 콜을 호출합니다.
Non Block인 경우에는 바로 커널모드에서 응답을 EAGAIN or EWOULDBLOCK
을 내려주고 호출한 쓰레드 A는 다른 작업을 할 수 있습니다.
내부 커널에서는 파일 I/O가 완료되면 다시 쓰레드 A는 재차 확인을 하기 위해 read
시스템 콜을 호출하여 데이터 조회를 합니다.
데이터가 있는 경우 데이터를 받아서 나머지 로직을 실행합니다.
Non-block I/O 이슈
I/O 작업 완료를 어떻게 확인할까?
- 완료됐는지 반복적으로 확인하기
- 완료된 시간과 완료를 확인한 시간 사이의 갭(
time gap
)으로 인해 처리 속도가 느려질 수 있음 - 완료됐는지 반복적으로 확인하는 과정이 발생합니다 CPU 낭비가 발생
소켓이 많다면 불필요한 확인 작업이 CPU가 필요하게 된다.
- 완료된 시간과 완료를 확인한 시간 사이의 갭(
- I/O multiplexing
관심있는 I/O 작업들을 동시에 모니터링하고 그 중에 완료된 I/O 작업들을 한번에 알려준다.- 여러 이벤트를 한번에 처리하는 방식입니다.
- select, poll, epoll, kqueue, IOCP(I/O completion port) 방식이 있습니다.
- Callback:
- signal
IO(Blocking) 방식
- IO(Blocking) 방식은 각 요청을 처리하기 위해 별도의 스레드를 할당합니다.
- 많은 요청이 들어온다면 그만큼 필요한 스레드의 숫자가 늘어납니다.
- 스레드의 숫자가 늘어나게 되면 CPU 컨텍스트 스위칭이 자주 발생하게 됩니다.
NIO(Non-blocking) 방식
- NIO는 매번 새로운 스레드를 생성하는 것이 아닙니다.
- 채널(Channel): 통신할 대상(파일, 네트워크 등)을 선정합니다.
- 버퍼(Buffer): 데이터를 주고받을 때 사용하는 임시 저장소이며, 크기를 지정합니다.
- 셀렉터(Selector): 채널에서 I/O 이벤트가 발생할 때마다 이를 감시하고, 해당 이벤트를 처리할 준비가 된 채널을 감지합니다.
동작 방식
-
채널과 버퍼 설정
- 채널을 통해 통신할 대상을 선정하고, 버퍼를 사용하여 데이터를 주고받습니다.
-
이벤트 감시
- 채널에서 I/O 이벤트가 발생하면 Selector가 이를 감지합니다.
- Selector는 주로 네트워크 소켓과 같은 비동기 이벤트 처리를 할 때 사용됩니다.
-
이벤트 처리
Selector가 이벤트를 감지하면, 해당 채널을 처리할 준비가 된 채널 리스트에 추가합니다.
- 하나의 스레드가 Selector를 통해 준비된 채널의 이벤트를 처리합니다. 이는 임시 보관소(버퍼)에 있는 데이터를 읽거나 쓰는 작업을 포함합니다.
- IO(Blocking) 방식은 각 요청마다 별도의 스레드를 할당하여 처리하며, 많은 요청이 들어오면 스레드 수가 늘어나고 컨텍스트 스위칭이 자주 발생합니다.
- NIO(Non-blocking) 방식은 하나의 스레드가 여러 채널을 관리하고, Selector를 통해 이벤트를 감시하고 처리합니다. 이는 효율적으로 I/O 작업을 처리하며, 스레드 수를 최소화하여 컨텍스트 스위칭을 줄입니다.
NIO의 Direct Buffer와 대용량 처리
- java.io 방식은 파일 작업을 하기 위해 데이터를 JVM 힙 메모리에 올리고, 이를 네이티브 메모리로 복사하는 과정이 발생합니다.
- 하지만 java.nio에서 Direct Buffer를 사용하면, 데이터를 JVM 힙 메모리에 복사하지 않고 네이티브 메모리 영역에 직접 할당합니다.
- 이로 인해 대용량 데이터를 주고받는 경우 복사 과정이 생략되어 성능상 이점이 있습니다.
네이티브 메모리의 장점
- 자유로운 메모리 확장성: 네이티브 메모리는 운영체제에서 관리되므로 힙 메모리보다 확장이 용이합니다.
- 성능 향상: 특히 Direct Buffer를 사용할 경우, 네이티브 IO 작업에서 메모리 복사 오버헤드가 줄어듭니다.
- JVM 힙 메모리 부담 감소: 네이티브 메모리를 사용함으로써 JVM 힙 메모리의 부담을 줄일 수 있습니다.
네이티브 메모리의 단점
- 높은 초기화 비용: 네이티브 메모리 할당은 JVM 힙 메모리 할당보다 초기화 비용이 높습니다.
- 복잡한 메모리 관리: 네이티브 메모리는 JVM의 가비지 컬렉션에 의해 관리되지 않으므로, 명시적인 메모리 해제가 필요합니다.
- 플랫폼 특성: 플랫폼에 따라 성능상 이점이 달라질 수 있습니다.
결론
자주 사용되는 대용량 I/O 작업의 경우, NIO의 Direct Buffer를 사용하는 것이 성능상 유리할 수 있습니다.
추가로 만약 라인으로 데이터를 읽는 경우라면 일반적인 IO 방식이 속도가 더 빠릅니다.
참조
- https://funnelgarden.com/java_read_file/
- https://taes-k.github.io/2021/01/06/java-nio/
댓글남기기