새로운 내용을 공부할 때
새로운 내용의 공부를 시작할 때 용어의 정의를 이해하지 못하거나 정확하게 알지 못한다면 그 용어가 포함된 문장을 이해하지 못합니다.
작은 단어 하나가 내용을 이해하지 못하게 하기 때문에 용어를 정확하게 이해하는 것이 중요합니다.

2 분 소요

목표 D-day : 84 일 리마인트 12회

학습목표

  • Block
  • Non-Block
  • Serializable
  • TCP/IP

Block I/O

소켓은 send-bufferrecv_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 작업 완료를 어떻게 확인할까?

  1. 완료됐는지 반복적으로 확인하기
    • 완료된 시간과 완료를 확인한 시간 사이의 갭(time gap)으로 인해 처리 속도가 느려질 수 있음
    • 완료됐는지 반복적으로 확인하는 과정이 발생합니다 CPU 낭비가 발생
      소켓이 많다면 불필요한 확인 작업이 CPU가 필요하게 된다.
  2. I/O multiplexing
    관심있는 I/O 작업들을 동시에 모니터링하고 그 중에 완료된 I/O 작업들을 한번에 알려준다.
    • 여러 이벤트를 한번에 처리하는 방식입니다.
    • select, poll, epoll, kqueue, IOCP(I/O completion port) 방식이 있습니다.
  3. Callback:
  4. signal

img

IO(Blocking) 방식

  • IO(Blocking) 방식은 각 요청을 처리하기 위해 별도의 스레드를 할당합니다.
  • 많은 요청이 들어온다면 그만큼 필요한 스레드의 숫자가 늘어납니다.
  • 스레드의 숫자가 늘어나게 되면 CPU 컨텍스트 스위칭이 자주 발생하게 됩니다.

NIO(Non-blocking) 방식

  • NIO는 매번 새로운 스레드를 생성하는 것이 아닙니다.
  • 채널(Channel): 통신할 대상(파일, 네트워크 등)을 선정합니다.
  • 버퍼(Buffer): 데이터를 주고받을 때 사용하는 임시 저장소이며, 크기를 지정합니다.
  • 셀렉터(Selector): 채널에서 I/O 이벤트가 발생할 때마다 이를 감시하고, 해당 이벤트를 처리할 준비가 된 채널을 감지합니다.

동작 방식

  1. 채널과 버퍼 설정

    • 채널을 통해 통신할 대상을 선정하고, 버퍼를 사용하여 데이터를 주고받습니다.
  2. 이벤트 감시

    • 채널에서 I/O 이벤트가 발생하면 Selector가 이를 감지합니다.
    • Selector는 주로 네트워크 소켓과 같은 비동기 이벤트 처리를 할 때 사용됩니다.
  3. 이벤트 처리

    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/

댓글남기기