자바 IO
- 자바 1 버전에 최초 도입
- 파일과 네트워크에 데이터를 읽고 쓸 수 있는 API 제공
- InputStream, OutputStream과 같이 byte 단위로 읽고 쓸 수 있는 stream
- blocking 방식으로 동작
1. InputStream
- 바이트 스트림을 통해 데이터를 읽는 데 사용되는 추상 클래스
- Closable 구현체이기 때문에 명시적으로 스트림을 닫거나 try-with-resources 사용 가능
- 파일, 네트워크 연결, 메모리 버퍼 등 다양한 소스로부터 데이터를 읽을 수 있는 표준화된 방법을 제공
- 어떤 source로부터 데이터를 읽을지에 따라 다양한 구현체 존재
- FileInputStream
- ByteArrayInputStream
- BufferedInputStream
- SocketInputStream
1.1 주요 메서드
- read(): stream으로 데이터를 읽고 읽은 값을 반환, -1이라면 끝에 도달했다는 것을 의미
- close(): stream을 닫고 더 이상 데이터를 읽지 않음
- available(): 스트림에서 읽을 수 있는 바이트 수를 반환 (읽지 않고도 읽을 수 있는 데이터 양 확인 가능)
- skip(long n): 스트림에서 n 바이트를 건너뛰고 실제 건너뛴 바이트 수 반환
1.2 자바 IO를 이용한 소켓 통신 (InputStream 관점)
- serverSocket을 open 하여 외부의 요청을 수신
- bind.accept를 통해 serverSocket open을 준비
- accept가 끝나면 반환값으로 클라이언트의 socket을 전달
- 클라이언트 socket의 getInputStream으로 소켓의 inputStream 접근
- SocketInputStream은 public이 아니기 때문에 직접 접근이 불가하며 socket.getInputStream()으로만 접근 가능
- blocking 발생
2. OutputStream
- 바이트 스트림을 통해 데이터를 쓰기 위해 사용되는 추상 클래스로 write 시 바로 전송하지 않고 버퍼에 저장한 다음 일정량의 데이터가 모이면 한 번에 전달
- Closable 구현체이기 때문에 명시적으로 스트림을 닫거나 try-with-resources 사용 가능
- 파일, 네트워크 연결, 메모리 버퍼 등 다양한 목적지에 데이터를 쓰는 데 표준화된 방법을 제공
- 여러 desination에 데이터를 쓸지 여부에 따라 다양한 구현체 존재
- FileOutputStream
- ByteArrayOutputStream
- BufferedOutputStream
- SocketOutputStream
2.1 주요 메서드
- write(): stream으로 데이터를 출력
- flush(): 출력 스트림을 강제로 비워 버퍼에 저장된 모든 출력을 목적지로 보냄
- close(): 출력 스트림을 닫고 시스템 자원을 해제
2.2 자바 IO를 이용한 소켓 통신 (OutputStream 관점)
- serverSocket을 open 하여 외부의 요청을 수신
- bind.accept를 통해 serverSocket을 open할 준비
- serverSocket.accept() 호출로 클라이언트 접속을 대기하며 이 부분에서 blocking이 발생
- accept가 끝나면 반환값으로 클라이언트의 socket을 전달
- 클라이언트 socket의 getOutputStream을 호출하여 소켓의 OutputStream에 접근
- SocketOutputStream은 public이 아니기 때문에 직접 접근이 불가하며 socket.getOutputStream()으로만 접근 가능
- BufferedOutputStream을 사용하여 데이터를 클라이언트에게 전송하며 데이터를 전송하는 동안 blocking이 발생할 수 있음
3. 자바 IO의 한계
- 앞서 소켓 통신 예제에서 살펴봤듯이 동기 blocking으로 동작
- 애플리케이션이 read 호출 시 kernel이 응답을 돌려줄 때까지 아무것도 할 수 없음
- I/O 요청이 발생할 때마다 쓰레드를 새로 할당할 경우 쓰레드 생성 및 관리하는 비용과 context switching으로 인한 cpu 자원 소모
- 커널 버퍼에 직접 접근 불가하기 때문에 메모리 copy가 발생하고 이에 따른 지연이 발생
- 자바 IO는 사용자 공간의 버퍼에서 커널 공간의 버퍼로, 또는 그 반대로 데이터를 복사해야 하며 이는 시스템 호출을 통해 이루어지며, 메모리 복사가 빈번하게 발생
- 대용량 데이터를 전송하거나 여러 입출력 작업을 동시에 수행할 때 지연 발생
부연 설명
- 하드웨어에서 값을 읽어오면 disk controlller가 DMA를 통해 커널 버퍼에 값을 복사
- 커널 버퍼에서 jvm 버퍼로 복사하며 이 과정에서 cpu 자원 소모
- jvm 버퍼, jvm 메모리에 있기 때문에 gc 대상이 되고 이 또한 cpu 자원 소모
자바 NIO
- Java New Input/Output의 약자로 자바 4 버전에 최초 도입
- 기존의 java.io 패키지보다 더 효율적이고 유연한 I/O 작업을 제공하는 것을 목표로 구현됨
- non-blocking 지원
- selector, channel 도입으로 높은 성능 보장
자바 IO | 자바 NIO | |
데이터의 흐름 | 단방향 | 양방향 |
종류 | InputStream, OutputStream | Channel |
데이터 단위 | byte/character | buffer |
blocking 여부 | 동기 blocking만 가능 | non-blocking 지원 (blocking API도 존재) |
특이사항 | selector 지원 |
1. Channel과 Buffer
- 데이터 읽을 때 적절한 크기의 버퍼를 생성하고 채널의 read() 메서드를 사용하여 데이터를 버퍼에 저장
- 데이터를 쓸 때 먼저 버퍼에 데이터를 저장하고 채널의 write() 메서드를 사용하여 목적지로 전달
- 버퍼를 clear() 메서드로 초기화하여 다시 사용 가능
1.1 Buffer 위치 속성
- capacity: 버퍼가 저장할 수 있는 데이터의 최대 크기로 버퍼 생성 시 결정되며 변경 불가
- position: 버퍼에서 현재 위치를 가리키며 버퍼에서 데이터를 읽거나 쓸 때 해당 위치로부터 시작
- limit: 버퍼에서 데이털르 읽거나 쓸 수 있는 마지막 위치, limit 이후로 데이터를 읽거나 쓰기 불가
- mark: 현재 position 위치를 mark()로 지정할 수 있고 reset() 호출 시 position을 mark로 이동
- 0 <= mark <= position <= limit <= capacity
2. DirectByteBuffer, HeapByteBuffer
2.1 DirectByteBuffer
- JVM 힙 외부의 메모리 영역을 사용하여 버퍼를 직접 할당하는 버퍼
- native 메모리에 저장
- 커널 메모리에 복사를 하지 않으므로 데이터를 읽고 쓰는 속도가 빠름
- 힙 메모리를 덜 사용하므로, JVM의 gc 부하를 줄일 수 있음
- 비용이 많이 드는 system call을 사용하므로 네이티브 메모리 할당은 JVM 힙 메모리 할당보다 시간이 더 걸림
2.2 HeapByteBuffer
- JVM 힙 메모리에 저장하며 byte array를 랩핑
- 자바 IO처럼 커널 메모리에 복사가 일어나므로 데이터를 읽고 쓰는 속도가 느림
- 단, JVM의 gc에서 관리가 되므로 메모리 할당이 상대적으로 빠름
2.3 ByteBuffer 구분 방법
- DirectByteBuffer: allocateDirect() 함수로 생성 가능
- HeapByteBuffer: allocate(), wrap() 함수로 생성 가능
- isDirect() 메서드를 통해 구분 가능
3. SelectableChannel
- 자바 NIO의 비동기 I/O 기능을 지원하는 채널
- SelectableChannel은 non-blocking 모드로 설정 가능
- SelectableChannel은 Selector와 통합되어 사용되며 이를 통해 여러 채널의 I/O 이벤트를 감지하고 처리 가능
3.1 SelectableChannel 주요 메서드
- configureBlocking(false): serverSocketChannel의 accept, socketChannel의 connect 등이 non-blocking으로 동작
- register(Selector sel, int ops): 채널을 Selector에 등록하고, 감지할 I/O 작업을 지정
자바 NIO의 한계
- Polling 문제: 클라이언트 연결을 기다리는 while 루프와 같은 구조에서는 새로운 클라이언트가 연결될 때까지 CPU가 계속해서 루프를 돌며 accept를 호출, 각각의 쓰레드에서 read 가능한지 주기적으로 확인
- Thread Sleep: clientSocket == null인 경우 Thread.sleep(100)을 호출하여 CPU 사용을 줄이지만, 이는 정확한 시간 조절이 어렵고, 연결 지연을 초래
- 동기 I/O 작업: clientSocket.read(requestByteBuffer)와 같은 호출은 블로킹 I/O와 유사하게 작동하며 데이터를 읽을 수 있을 때까지 계속해서 대기
- 성능 문제: 동시에 발생하는 요청이 증가하는 경우 연결 처리가 순차적으로 발생하여 성능 저하
자바 AIO (NIO2)
- Java 7부터 도입된 NIO.2(New I/O 2) 라이브러리의 일부로, 비동기적 입출력을 지원하여 성능을 향상시키고 자원을 효율적으로 사용하도록 지원
- 기존의 블로킹 I/O와 대비하여, AIO는 스레드가 입출력 작업이 완료될 때까지 블로킹되지 않도록 하여 높은 동시성을 제공
- AsynchronousChannel 지원
1. 자바 AIO의 특징
- I/O가 준비되었을 때 Future 혹은 callback으로 비동기적인 로직 처리 가능
- 쓰레드 풀과 epool, kqueue 등의 이벤트 알림 system call 이용
- 이러한 시스템 콜은 커널에서 I/O 이벤트를 모니터링하고, I/O 작업이 준비되었을 때 이를 알림
- CPU 사용량을 최소화하고, 높은 성능을 제공
2. 자바 AIO vs 자바 NIO
자바 AIO | 자바 NIO | |
비동기 I/O 처리 | 1. 자바 AIO는 진정한 비동기 I/O 처리를 제공하여, I/O 작업이 완료될 때까지 기다리지 않고 바로 다른 작업을 수행할 수 있음 2. Future와 CompletionHandler를 사용하여 비동기 작업을 처리할 수 있어, 코드가 간결하고 이해하기 쉬움 |
1. 자바 NIO에서는 I/O 작업이 완료될 때까지 대기하는 블로킹 방식으로 데이터를 읽고 쓸 수 있으며 이는 큰 데이터를 처리할 때 성능 병목을 초래할 수 있음 2. Selector와 Channel을 사용하여 비동기적으로 처리할 수 있지만, 코드가 복잡해지고 관리가 어려움 |
자원 효율성 및 성능 | 1. 자바 AIO는 내부적으로 쓰레드 풀을 사용하여 비동기 작업을 처리하므로, 많은 클라이언트 요청을 효율적으로 처리 2. epoll, kqueue와 같은 시스템 콜을 사용하여 I/O 이벤트를 모니터링하므로, CPU 사용량을 최소화하고 높은 성능을 제공 |
1. 자바 NIO는 많은 클라이언트 연결을 처리할 수 있지만, 클라이언트 수가 많아질수록 Selector와 Channel을 관리하는 데 부담이 증가함 2. 이벤트 처리 루프에서 발생하는 불필요한 폴링과 블로킹으로 인해 CPU 사용량이 증가 |
코드 간결성 및 유지보수성 | 1. 자바 AIO는 CompletionHandler와 Future를 사용하여 비동기 작업을 간단하게 처리할 수 있으며 코드가 간결하고 명확하여 유지보수가 용이해짐 2. 콜백 메서드를 사용하여 이벤트 발생 시 필요한 작업을 처리하므로, 코드 구조가 더 직관적이고 이해하기 쉬움 |
1. 비동기 I/O 작업을 구현하려면 Selector와 Channel을 사용하여 복잡한 이벤트 처리 루프를 작성해야 하므로, 코드가 복잡해지고 유지보수가 어려움 |
3. 자바 NIO로 작성한 멀티 클라이언트 수용 서버 코드에 자바 AIO 적용
참고
- 패스트 캠퍼스 - Spring Webflux 완전 정복 : 코루틴부터 리액티브 MSA 프로젝트까지
반응형
'JAVA > 비동기 프로그래밍' 카테고리의 다른 글
[Netty] Netty 개요 및 간단한 예제 (0) | 2024.07.27 |
---|---|
[Java] CompletableFuture (0) | 2024.05.12 |
[Java] ThreadPoolExecutor (1) | 2024.04.28 |
[Java] 자바 동시성 프레임워크 (4) | 2024.04.20 |
[Java] 동기화 도구 (0) | 2024.03.14 |