JAVA I/O - Stream & BufferedStream vs InputStream 그리고 NIO의 등장

Yunny.Log ·2023년 7월 2일
1

JAVA

목록 보기
29/29
post-thumbnail

파일 입출력

JAVA I/O

  • 초기 단계의 자바에서는 I/O를 처리하기 위해 java.io 패키지에 있는 클래스를 제공했다.
  • 바이트 기반의 데이터를 처리하기 위해 여러 종류의 Stream이라는 클래스를 제공한다.
  • char 기반의 문자열로만 되어있는 파일은 Reader/Writer를 사용한다.

자바에서 데이터 입출력은 Stream을 통해 이루어진다. 프로그램을 기준으로 들어오면 Input(읽기), 내보내면 Output(쓰기)로 방향을 결정하고, 데이터의 교환을 위해서는 Input과 Output을 모두 만들어야 한다. 입출력과 관련된 모든 작업은 java.io 패키지에서 제공하고 있다.

Input/Output Stream

  • 바이트 기반 입출력 클래스의 최상위 클래스이자 추상클래스이다. 입출력할 데이터가 OS와 JVM을 거쳐 메모리에 1byte씩 전달된다. 이는 마치 쇼핑몰에서 주문한 상품이 모든 지역의 구매자에게 하나하나씩 전달되는 것과 같다.

Buffered Input/Output Stream

  • 필터 클래스 중에 버퍼(Queue 구조로 되어있는 임시 저장소)를 제공하는 클래스. App안에 default 2MB 짜리 버퍼를 생성해서 버퍼가 File을 한번에 받아준 후 1byte씩 메모리에 전달된다. 쇼핑몰에서 주문한 상품들이 지역 담당의 택배기사(버퍼)에게 전달되어 택배기사가 그 지역 안의 구매자들에게 전달해주는 것과 같다. 이동 경로가 단축되어 시간 cost가 현저히 줄어든다.

NIO의 등장 (Non-blocking IO)

기존 IO의 문제점

Java I/O PROCESS

  • 프로세스가 파일을 읽기 위해 커널 명령을 전달한다.
  • 커널은 시스템 콜(read)을 사용한다.
  • 디스크 컨트롤러가 물리적 디스크로부터 파일을 읽어온다.
  • DMA에 저장된 데이터를 다시 커널 버퍼 메모리 영역에 복사한다.
  • CPU 인터럽트가 일어나면 CPU는 커널 버퍼 메모리 영역에 복사된 데이터를 프로세스 - 내부 버퍼 메모리에 복사한다.

문제점

  • 커널에서 JVM 내부로 데이터를 복사할 때 CPU 연산을 계속해서 사용하게 되고 이렇게 복사된 데이터들은 GC 대상이 되므로 계속해서 오버헤드가 발생하게 된다.
  • 커널 영역에서 데이터를 복사하는 동안에 프로세스 영역이 Blocking 되면서 전체 처리 속도가 느려진다.

NIO(New IO)

  • JDK 1.4 부터 도입되어 기존 IO의 속도 문제를 해결했다.
  • Java 7부터 IO와 NIO 사이의 일관성 없는 클래스 관계를 바로 잡고 비동기 채널 등 네트워크 지원을 강화한 NIO.2 API가 추가되었다.
  • 스트림 대신 ChannelBuffer 를 사용한다.
public void writeFile(String fileName, String data) throws Exception {
    FileChannel channel = new FileOutputStream(fileName).getChannel();
    byte[] byteData = data.getBytes();
    ByteBuffer buffer = ByteBuffer.wrap(byteData);
    channel.write(buffer);
    channel.close();
}

IO와 NIO의 차이점을 통해 알아보는 NIO가 IO를 개선한 방법

구분IONIO
입출력 방식스트림채널
버퍼 방식넌버퍼(Non-Buffer)버퍼(Buffer)
비동기 방식지원 안 함지원
블로킹 / 넌블로킹 방식블로킹 방식만 지원블로킹 / 넌블로킹 방식 지원

1. Channel

IO에서는 데이터를 읽기 위해서 입력 스트림을 생성하고 출력하기 위해 출력 스트림을 생성해야 했지만 채널에서는 입력과 출력을 위해 별도의 채널을 만들 필요가 없다.
NIO는 채널 기반으로 스트림과 달리 양방향으로 입력과 출력이 가능하며 쓰레드와 버퍼 사이에서 일종의 터널 역할을 해서 입출력 기능을 수행한다.


2. Buffer

기존 IO에서도 Buffer로 한꺼번에 입력받고 출력하기 위해 BufferdInputStream과 같은 확장 클래스를 사용하기도 했는데
NIO에서는 기본적으로 버퍼를 사용해서 입출력을 하기 때문에 효율적으로 처리가 가능하고 데이터의 위치를 이동하면서 필요한 부분을 읽고 쓸 수 있다.


버퍼가 사용하는 메모리 위치에 따른 분류

구분Non-direct bufferDirect buffer
사용하는 메모리 공간JVM의 힙 메모리운영체제의 메모리
버퍼 생성 시간빠르다느리다
버퍼의 크기작다크다
입출력 성능낮다높다

Direct Buffer

운영체제에서 생성되므로 여러가지 처리가 필요해 속도가 느리다.
Channel을 사용해서 버퍼의 데이터를 읽고 저장할 경우에만 운영체제의 native IO를 수행한다.
만약 Channel을 사용하지 않고 사용한다면 내부적으로 JNI를 호출해야 하기 때문에 오버 헤더가 추가된다.
NIO의 FileChannelDirect Buffer를 활용하면 자원 사용량을 줄일 수 있다.


3. Blocking / Non-Blocking

IO는 쓰레드가 Blocking이 되면 인터럽트도 발생시킬 수 없고 빠져나오기 위해서는 스트림을 닫아야만 한다.
하지만 NIO는 Blocking 시 인터럽트로 빠져나올 수 있고, Non-Blocking으로 입출력 작업 준비가 완료된 채널만 선택할 수 있도록 지원해서
작업 중인 쓰레드가 Blocking 되지 않게 한다. 이때 사용하는 것이 Selector 이다.


Selector

image

네트워크 프로그래밍의 효율을 높이기 위해 도입된 것으로 한 개의 쓰레드로 다수의 채널을 처리할 수 있는 기술이다.

Selector에 여러 채널이 등록되면 쓰레드는 셀렉터에 등록된 채널 중에 가용한 채널이 있는지 알 수 있고 이런 방식을 멀티플렉싱(Multiplexing)이라고 한다.


4. Scatter/Gather

시스템 콜을 여러 번 호출하는 비효율적인 작업을 개선하기 위해 ScatterGather로 프로세스에서 사용한 버퍼 목록들을 한 번에 넘긴다.
즉, 운영체제에서 최적화된 로직에 따라 버퍼들을 순차적으로 처리해 데이터를 읽고 쓸 수 있다.


5. 가상 메모리

프로세스 내 버퍼의 가상 주소와 커널 영역 버퍼의 가상 주소가 같은 물리 메모리를 참조하게 매핑시켜 데이터 복사 작업을 생략할 수 있다.(CPU blocking 대기 시간 X)
해당 OS 기술은 Zero-copy라고 부른다.

  • 장점
    • 실제 메모리 크기보다 큰 가상 메모리 공간 사용 가능
    • 여러 개의 가상 주소가 하나의 물리 메모리를 참조하기 때문에 메모리 효율적 접근 가능
  • 메모리 맵 파일 : 두 가상 메모리가 참조하는 물리 메모리 중 디스크의 내용까지 일치시키는 기술

6. File lock

한 프로세스가 어떤 파일에 락을 획득했을 때 다른 프로세스가 해당 파일을 동시에 접근하지 못하게 제한하는 기능이다.

  • 파일 전체 혹은 일부분에 락을 걸 수 있고 바이트 단위로 계산한다.
  • 일부분에 락이 걸려있을 때, 나머지 위치에서는 여러 프로세스들이 동시에 다른 작업을 할 수 있다.

IO와 NIO를 선택하는 기준

  • 비동기적으로 처리할 수 있는 작업이 아니라면 NIO를 사용했을 때 오히려 Non-Blocking 때문에 생기는 추가 동작으로 속도만 더 느려질 수 있다.
  • 대용량의 데이터를 처리할 때는 NIO로 모든 입출력에 Buffer를 사용하기 보다는, IO를 선택함으로써 스레드의 개수를 늘려 Blocking으로 처리해야 메모리 낭비를 줄일 수 있고 오버헤드를 줄일 수 있다.

응용 질문

  • NIO가 IO의 어떤점을 개선한 것인지? 왜 개선했는지?

    기존 IO의 속도와 Blocking으로 인한 성능 저하를 개선하기 위해서 여러가지 방식을 도입했습니다.
    하나의 채널로 다수의 입출력 작업을 수행할 수 있고 버퍼를 사용하여 속도를 개선했으며,
    Non-Blocking 방식을 지원하면서 Selector를 이용하여 가용한 채널에 접근할 수 있습니다.


  • 버퍼를 쓰는 이유와 장점

    시간을 절약할 수 있고 데이터의 필요한 부분을 읽거나 쓸 수 있기 때문입니다. 데이터를 버퍼 공간에 담아놨다가 공간이 가득차면 데이터를 저장하기 때문에
    사용하지 않을 때보다 시간을 절약할 수 있고 Buffer의 position을 이용해서 위치를 파악하면 필요한 부분의 데이터를 읽거나 쓸 수 있습니다.
    운영체제 관점에서 버퍼를 사용하지 않았을 때 CPU Block이 계속해서 발생하기 때문에 속도가 느린데 버퍼를 사용하면 CPU 부하를 줄일 수 있습니다.


  • 파일 다운로드 시 IO/NIO 중 선택할 방식과 이유

    IO를 선택하겠습니다. 파일 다운로드 시에는 데이터를 읽어야만 저장할 수 있으므로 비동기적으로 처리해야 할 작업이 필요하지 않기 때문에 Non-Blocking으로 인한 추가 작업을 수행하지 않고 리소스를 절약하며 기능을 수행할 수 있을 것 같습니다.

>

0개의 댓글