운영체제를 모르는 개발자는 땅의 구조를 모른 채 건물을 짓는 사람과 같다. 실무에서 메모리 부족, CPU 점유율 급등, 스레드 병목 같은 문제를 처음 만났을때 대부분 이렇게 느낀다.
운영체제(OS)란?
운영체제는 컴퓨터 하드웨어와 소프트웨서 사이에서 통역사 역할을 하는 프로그램이다. 우리가 작성한 코드가 실제 컴퓨터에서 "언제", "어떻게" 실행될지 결정하는 건 바로 이 운영체제다.
운영체제가 하는 일 :
- 프로그램이 CPU를 사용할 수 있도록 순서 정하기(스케줄링)
- 여러 프로그램이 동시에 돌아가도 문제없게 메모리 나눠쓰기
- 파일을 읽고 쓰는 걸 도와주는 파일 시스템 관리
- 사용자와 하드웨어 장치(키보드, 프린터 등) 사이 중재
운영체제 핵심 개념 5가지
1. 프로세스 vs 스레드
구분 | 프로세스 | 스레드 |
정의 | 실행 중인 프로그램 | 프로세스 내의 작업 단위 |
메모리 | 독립된 메모리 공간 | 같은 메모리 공간 공유 |
충돌 시 | 하나 죽어도 다른 프로세스 영향 없음 | 하나가 죽으면 전체 영향 가능 |
2. CPU 스케줄링
운영체제는 모든 프로그램이 CPU를 동시에 사용할 수 없기 때문에 누구를 먼저, 얼마나 오래 실행할지 결정한다.
- FCFS (First Come First Serve) : 먼저 온 순서대로
- Round Robin : 시간을 나눠서 조금씩 번갈아 실행
- Priority Scheduling : 우선순위 높은 작업 먼저 실행
실무에서 배치 작업이 느리거나 웹 요청 응답이 늦을 때, 내부적으로는 CPU 스케줄링이 원인일 수 있다.
3. 메모리 관리
운영체제는 여러 프로그램이 충돌하지 않도록 메모리를 구획을 나눠 관리한다.
- Stack / Heap / Data / Code 영역
- 가상 메모리 : 실제 물리 메모리가 부족할 때 디스크를 RAM처럼 쓰는 기술(swap 발생)
실무에서 발생하는 OutOfMemoryError, GC 지연 현상 등은 메모리 구간 이해가 핵심이다.
4. 동기화와 데드락
여러 스레드가 하나의 자원을 동시에 접근하면 문제가 생길 수 있다.
운영체제는 Lock이나 세마포어(Semaphore) 같은 방법으로 이를 막는다.
데드락 : A는 B의 리소스를, B는 A의 리소스를 기다리는 상황 -> 둘 다 멈춤
실무에서 DB 커넥션 풀, 멀티스레딩 환경에서 데드락은 진짜 흔하게 발생한다.
5. 인터럽트 (Interrupt)
컴퓨터는 외부의 신호 (예 : 키보드 입력, 하드디스크 응답 등)를 기다리지 않고, 인터럽트가 발생하면 즉시 반응한다.
- 동기 -> 순서대로 기다림
- 비동기(인터럽트 기반) -> 갑자기 들어오는 요청에도 빠르게 대응
실시간 처리가 중요한 서비스에서는 인터럽트를 효율적으로 처리하는 게 핵심이다.
왜 OS 지식이 필요한지 예시를 들어보자
fun main() {
val thread1 = Thread { heavyComputation() }
val thread2 = Thread { heavyComputation() }
thread1.start()
thread2.start()
}
위 코드에서 스레드 2개가 동시에 실행될 수 있을까???
정답은 아니요 이다. CPU 코어 수, 스케줄러 정책, OS가 허용하는 최대 스레드 수에 따라 실제 동작이 달라진다.
실무에서 마주치는 메모리 문제는 어떤게 있고, 어떻게 해결하는지도 한번 알아보자.
서비스는 점점 느려지고, 갑자기 서버가 죽었다? -> 90%는 메모리 문제라고 봐도 무방하다.
자바 개발자가 흔히 마주치는 메모리 이슈에는 다음과 같은 것들이 있다.
1. OutOfMemoryError (OOM)
JVM이 힙 공간을 더 이상 확보할 수 없을때 발생한다.
원인 :
- 무한 루프 안에서 List에 데이터 추가
- 캐시를 비워주지 않음
- 너무 많은 객체 생성(ex: 수천 개의 파일을 한 번에 처리)
해결전략 :
- JVM 힙 크기 조절 : -Xmx1024m
- 캐시 정책 설정 (예 : LRU, TTL)
- 대용량 작업 시 스트리밍 처리 (InputStream, Flux, Cursor)
2. GC 튜닝 문제로 인한 성능 저하
메모리는 충분한데 느리다?? -> GC에 시간이 다 잡아먹히고 있을 수 있다.
진단 방법. :
- -Xlog:gc* 옵션으로 GC 로그 분석
- jvisualvm, GCViewer, JFR (Java Flight Recorder) 사용
해결 전략 :
- G1 GC, ZGC 등 최신 GC 사용 고려
- 객체 생명 주기 짧게 설계 (가비지 생성을 줄이기)
- 메모리 재사용 (Object Pool 등)
3. PermGen / Metaspace 부족 (클래스 메타데이터 문제)
대규모 프로젝트, 플로그인 구조에서 자주 발생하는 문제이다.
원인 :
- 클래스 로더 누수
- 동적 클래스 생성(ex: JSP, Proxy)
해결 전략 :
- -XX:MaxMetaspaceSize 설정
- 코드 HotReload 시 reload 제한
- 메모리 분석 툴로 클래스 로더 누수 확인(jmap, MAT)
메모리 문제 해결을 위한 필수 도구 모음
도구 | 용도 |
jamap | 힙 덤프 추출 |
jhat, Eclipse MAT | 힙 덤프 분석 |
jstat | GC/메모리 상태 확인 |
VisualVM, JFR | 실시간 분석 |
Netdata, Prometheus + Grafana | 전체 시스템 메모리 모니터링 |
실전 트러블슈팅 사례 한가지만 살펴보자
무한 수집 중 OutOfMemoryError 가 발생한 케이스다.
문제 상황 : 외부 API에서 대량의 데이터를 수집하여 List에 저장 -> 메모리 부족(OOM) 사용자 요청은 점점 늘고, 서버는 점점 느려지다 뻗어버림
문제 코드 (잘못된 예시)
@RestController
class LogCollectorController {
val collectedLogs = mutableListOf<String>() // 무한히 쌓임 → 힙 폭발
@GetMapping("/collect")
fun collect(): String {
val externalLogs = callExternalApi() // 외부에서 로그 1000건씩 수신
collectedLogs.addAll(externalLogs) // 계속 메모리에 추가만 됨
return "Collected ${externalLogs.size} logs"
}
fun callExternalApi(): List<String> {
// 외부 로그 API 시뮬레이션
return List(1000) { "log line $it" }
}
}
문제점
- colletedLogs에 데이터를 계속 저장한다.
- GC가 수거하지 못하는 상태로 누적된다 -> OOM 발생
- 서버는 정상적으로 작동하는 것처럼 보이지만, 메모리는 계속 쌓임
해결 코드 (스트리밍 처리 + 즉시 저장)
@RestController
class LogCollectorController(val logService: LogService) {
@GetMapping("/collect")
fun collect(): String {
val externalLogs = callExternalApi()
// 스트리밍 처리로 한 건씩 바로 저장하여 메모리 사용 최소화
externalLogs.forEach { log ->
logService.save(log)
}
return "Collected ${externalLogs.size} logs"
}
fun callExternalApi(): Sequence<String> {
// Sequence로 lazy하게 한 줄씩 처리
return generateSequence(0) { it + 1 }
.take(1000)
.map { "log line $it" }
}
}
@Service
class LogService {
fun save(log: String) {
// DB 또는 파일 저장 등의 실질적인 처리
println("Saving log: $log")
}
}
개선 포인트
- Sequence 사용 : List 에 다 넣지 않고, 한 줄씩 처리하므로 메모리 부담이 내려간다.
- 즉시 저장 : GC가 객체를 빠르게 수거 가능하다
- 상태 유지 X : collectedLogs와 같은 전역 메모리 누적 제거
메모리 문제는 버그보다 무섭다. 이유는 단순한데 느리거나 죽기 때문이다. 운영 중인 시스템이라면 예방이 가장 중요하고, 대응할 때는 반드시 근거 있는 추측과 도구 기반 분석이 필요하다.
'컴퓨터구조와 운영체제' 카테고리의 다른 글
운영체제의 큰 그림 (1) | 2024.08.28 |
---|---|
운영체제를 알아야 하는 이유 (1) | 2024.08.14 |
장치 컨트롤러와 장치 드라이버 (0) | 2024.08.13 |
RAID 정의와 종류 (0) | 2024.08.08 |
보조기억장치 (0) | 2024.07.20 |