Post

언어 & 런타임 | Language & Runtime

언어 & 런타임 | Language & Runtime

내가 볼려고 작성한 CS 공부.

⚙️ 언어 & 런타임

✅ 1. 컴파일러 vs 인터프리터

1.1 프로그램은 바로 실행되지 않는다?

우리가 쓰는 Python, Java, C 같은 프로그래밍 언어는 사람이 이해하기 쉬운 언어다.
하지만 컴퓨터는 오직 0과 1 (기계어) 만 이해할 수 있어.

→ 그래서 반드시 “사람이 쓴 코드 → 기계가 이해하는 코드”로 변환하는 과정이 필요함!
이때 등장하는 게 바로:

  • 컴파일러(Compiler)
  • 인터프리터(Interpreter)

1.2 컴파일러란?

컴파일러는 전체 소스 코드를 한 번에 기계어로 번역한 다음,
그 결과물(실행 파일)을 실행하는 방식이다.

예: C 언어 실행 흐름

  1. 사람이 작성한 .c 파일
  2. 컴파일러가 .exe 또는 .out 실행 파일로 번역
  3. 이 실행 파일은 바로 컴퓨터에서 실행 가능

💡 컴파일 과정에서 문법 오류가 있으면 실행조차 안 됨

1.3 인터프리터란?

인터프리터는 소스 코드를 한 줄씩 읽고, 즉석에서 실행하는 방식이다.
“번역과 실행을 동시에 한다”고 생각하면 돼.

예: Python 실행 흐름

  1. print("Hello") 입력
  2. 인터프리터가 해당 줄을 즉시 해석하고 실행
  3. 다음 줄로 넘어감

💡 문법 오류가 있어도, 해당 줄에 도달하기 전까지는 실행됨

1.4 컴파일러 vs 인터프리터 비교

항목컴파일러인터프리터
동작 방식전체 소스 → 기계어로 컴파일 후 실행한 줄씩 즉석 해석 & 실행
실행 속도빠름 (이미 기계어 상태)느림 (실시간 해석)
오류 처리모든 오류를 컴파일 시점에 체크실행 중 해당 줄에서만 발생
디버깅어려움 (전체 다시 컴파일해야 함)쉬움 (즉시 수정 가능)
예시 언어C, C++Python, JavaScript

1.5 실제 언어들은 섞어서 사용한다?

현실에서는 많은 언어가 컴파일 + 인터프리터 혼합 방식을 사용한다.

예시 1: Java

  1. .java 소스 코드를 컴파일해서 .class 바이트코드로 변환
  2. 실행 시 JVM이 그걸 인터프리팅하거나 JIT 컴파일

Java는 “컴파일 언어지만 인터프리터적인 런타임을 가짐”

예시 2: Python

  • 보통은 인터프리터 기반이지만, 일부 최적화된 Python(JIT 기반 PyPy 등)은 컴파일도 함

PyPy란?

  • PyPy는 Python 언어를 빠르게 실행하기 위한 인터프리터.
  • Python으로 만든 프로그램을 더 빠르게 실행할 수 있게 해줌.
  • 보통 사용하는 Python은 CPython.
  • PyPy는 Just-In-Time (JIT) 컴파일러를 사용해서 성능을 높임.

CPython vs PyPy | 항목 | CPython (기본) | PyPy (대체 구현체) | |————-|—————————–|——————————| | 속도 | 보통 | 더 빠름 (특히 반복 작업) | | 구현 언어 | C 언어 | RPython (Python의 서브셋) | | JIT 지원 | ❌ 없음 | ✅ 있음 | | 호환성 | 거의 모든 라이브러리 OK | 일부 C 확장 호환 어려움도 | | 설치 | python | 별도 설치 (pypy) 필요 |

언제 PyPy 쓰는가?

  • 반복이 많은 연산 (루프-heavy) 코드 → 속도 이득 큼
  • 순수 Python으로 짠 코드 → 최적화 잘 됨
  • 성능 병목 있는 코드 → PyPy로 실험해보면 좋음

그외 파이썬 인터프리터

  • Jython, IronPython, MicroPython

1.6 언제 컴파일러? 언제 인터프리터?

조건적합한 방식설명
실행 속도 최우선컴파일러이미 기계어이므로 빠름
빠른 개발 & 테스트인터프리터즉시 실행, 빠른 피드백
코드가 복잡하고 안정성 중요컴파일러전체 오류 사전 체크
간단한 스크립트나 자동화인터프리터실행 환경만 있으면 끝

1.7 그림으로 정리하면?

컴파일러 방식 (C)

1
[코드 작성] → [컴파일러] → [실행 파일 생성] → 실행

인터프리터 방식 (Python)

1
[코드 작성] → [인터프리터] → 즉시 실행

1.8 왜 중요한가?

언어가 컴파일러 기반인지, 인터프리터 기반인지에 따라:

  • 실행 속도
  • 오류 처리 방식
  • 배포 방식
  • 디버깅 편의성

이 전부 달라지기 때문에,
개발 도구나 서비스 선택 시에도 큰 영향을 준다.

✅ 2. 스택 & 힙 메모리 구조

2.1 프로그램은 실행될 때 메모리를 나눠 쓴다

코드를 실행하면, 운영체제는 메모리(RAM) 공간을 여러 영역으로 나눈다.

주요 메모리 영역

영역설명
코드(Code)실행될 명령어 저장
데이터(Data)전역 변수, 정적 변수 저장
스택(Stack)함수 호출 정보, 지역 변수 저장
힙(Heap)동적으로 생성된 객체 저장

2.2 스택(Stack)이란?

함수 실행 정보와 지역 변수를 저장하는 영역
후입선출(LIFO) 구조이며, 함수가 호출될 때마다 스택에 쌓이고, 종료되면 빠진다.

예시

1
2
def func():
   a = 10 # 스택에 저장 func()
  • a는 함수 안에서만 유효
  • 함수 종료 시 스택에서 자동 제거됨

💡 스택은 매우 빠르지만, 메모리 크기가 작고 자동 소멸함

2.3 힙(Heap)이란?

동적 메모리 할당(필요할 때 수동 생성) 을 위한 영역
객체나 리스트 같은 데이터들이 저장된다.

예시

1
arr = [1, 2, 3] # 힙에 저장됨
  • arr는 힙에 저장되며, 함수 종료돼도 메모리 해제되지 않음
  • 프로그래머가 직접 관리하거나, 가비지 컬렉터(GC)가 처리

💡 힙은 크고 유연하지만, 접근 속도는 스택보다 느림

2.4 스택 vs 힙 비교

항목스택(Stack)힙(Heap)
할당 시점함수 호출 시 자동명시적 할당 (new 등)
저장 대상지역 변수, 호출 정보객체, 배열 등
해제 방식자동수동 또는 GC
속도매우 빠름상대적으로 느림
오류 사례Stack OverflowMemory Leak

2.5 자주 발생하는 오류

Stack Overflow

함수를 무한히 재귀 호출하는 경우

1
2
def foo(): 
   foo() # 무한 재귀 foo()

→ 호출 정보가 계속 쌓여 스택이 꽉 차며 오류 발생

Memory Leak

힙에 저장된 데이터를 더 이상 사용하지 않는데도
참조가 끊기지 않아 GC가 제거하지 못하는 상황

  • 전역 변수로 계속 참조 유지
  • 이벤트 리스너 해제 안 함
  • 캐시에 쌓이고 제거되지 않음

2.6 왜 중요할까?

이 구조를 이해해야…

  • 함수 호출 원리 (재귀가 왜 위험한지)
  • 객체가 언제까지 메모리에 남는지
  • 메모리 최적화가 왜 필요한지

를 알 수 있다.

특히, C/C++처럼 메모리를 직접 다루는 언어에서는
스택/힙 구조를 모르면 버그와 보안 취약점이 쉽게 생긴다!

✅ 3. 가비지 컬렉션 (Garbage Collection)

3.1 가비지란?

가비지(Garbage)란 더 이상 사용하지 않는 객체
즉, 프로그램 안에서 누구도 참조하지 않는 메모리 조각

예시

1
2
a = [1, 2, 3] 
a = None # 원래 리스트는 더 이상 참조되지 않음 → 가비지

변수 a는 [1, 2, 3]이라는 리스트 객체를 참조(reference)했지만, 이제 None을 참조.
[1, 2, 3]이라는 리스트는 아무 변수도 참조하지 않게 되고, 그러면 그 리스트 객체는 쓸모없어진 데이터가 됨.
이런 데이터를 가비지(garbage) 라고 함

3.2 가비지 컬렉션이 필요한 이유

문제 상황설명
메모리 누수 (Memory Leak)쓸모없는 데이터가 계속 남아있으면 메모리 낭비
Dangling Pointer너무 일찍 메모리 해제 시 다른 곳에서 오류 발생

GC는 이런 위험을 줄이고, 프로그래머의 부담을 줄여줌

3.3 기본 방식 – Mark & Sweep

가장 오래된 GC 알고리즘: “표시하고 지운다”

단계

  1. Mark 단계: 더 이상 접근할 수 없는 객체를 탐색해서 표시
  2. Sweep 단계: 표시된 객체를 제거하여 메모리 회수

→ 루트 객체에서 연결된 것만 남기고 나머지는 제거

3.4 Generational GC (세대별 가비지 컬렉션)

대부분의 GC는 객체의 “나이”에 따라 관리 방식을 달리함

세대설명
Young Generation금방 생성된 객체 (많이 죽음)
Old Generation오래 살아남은 객체 (GC 적게 발생)

→ 자주 죽는 객체는 빨리 제거하고, 오래된 객체는 GC를 덜 자주 수행해 성능 최적화

3.5 Stop-the-world란?

GC를 실행하는 동안 프로그램 전체가 멈추는 현상

  • 메모리 수집이 끝나야 다음 명령 실행 가능
  • 사용자 입장에선 버벅이거나 멈춘 것처럼 느껴짐

→ GC의 효율이 중요해지는 이유

3.6 참조 카운팅 방식 (Python 등)

객체가 몇 개의 변수에서 참조되고 있는지를 기준으로 판단

예시

1
2
3
4
a = [1, 2] 
b = a # 참조 수 = 2 
a = None # 참조 수 = 1 
b = None # 참조 수 = 0 → 제거됨

단점: 순환 참조에서는 카운트가 0이 되지 않아 누수 발생
→ Python은 이를 보완하기 위해 주기적으로 순환 탐지도 수행함

  • 순환 참조: 객체들이 서로 참조 (a = b, b = a)
  • 순환 탐지: 순환 참조를 찾아 자동 탐지 기능

3.7 언어별 GC 요약

언어GC 특징
JavaMark & Sweep + Generational GC + 튜닝 가능
Python참조 카운트 + 순환 탐지
JavaScript최신 브라우저는 Mark & Sweep 방식
Go자동 GC + Pause 최소화
C / C++없음 (개발자가 직접 해제해야 함)

3.8 GC의 한계 – 만능은 아니다

자동이라고 해도 무조건 완벽하지 않음
개발자가 여전히 메모리 구조와 참조 관계에 주의해야 한다.

메모리 누수 예시

  • 전역 변수로 객체를 참조
  • 클로저 안에 불필요한 데이터가 남아있음
  • 이벤트 리스너를 제거하지 않음
  • 캐시된 객체를 비우지 않음

3.9 요약

  • GC는 힙에 존재하는 불필요한 객체를 자동으로 제거해주는 시스템
  • 가장 기본은 Mark & Sweep
  • 대부분의 언어는 Generational GC를 채택해 성능을 개선
  • 개발자가 GC가 있어도 메모리 구조를 잘 설계해야 함

✅ 4. JIT 컴파일 & 바이트코드

4.1 바이트코드란?

바이트코드는 사람이 읽기 어려운 중간 단계 코드로,
운영체제나 CPU에 독립적인 형태다.

왜 쓰는가?

  • 바로 기계어로 번역하면 OS/CPU마다 다르게 만들어야 함
  • 바이트코드는 하나만 만들고, 그걸 해석해주는 런타임(JVM, Python VM 등)이 실행함

예시

  • Java → .class (바이트코드) → JVM이 실행
  • Python → .pyc (바이트코드) → CPython이 실행

4.2 인터프리트 vs 바이트코드 기반 언어

방식설명
인터프리트코드 줄마다 해석 (Python, JavaScript 등)
바이트코드 기반컴파일 후 중간 코드(JVM, CLR 등)로 실행 (Java, Kotlin, C# 등)

바이트코드는 컴파일된 상태지만 기계어는 아님 → “중간단계” 코드

4.3 JIT 컴파일(Just-In-Time)이란?

JIT 컴파일은 프로그램 실행 중, 바이트코드를 기계어로 실시간 변환하는 기술이야.

흐름

  1. 처음에는 바이트코드를 인터프리팅
  2. 자주 실행되는 코드를 감지
  3. 그 부분만 기계어로 컴파일해서 캐싱
  4. 이후엔 빠르게 실행 가능

즉, 실행 중 최적화하는 똑똑한 컴파일러

4.4 JIT의 장점

항목설명
실행 속도 향상자주 쓰는 부분만 최적화 → 성능 UP
런타임 정보 활용실제 데이터 패턴에 맞춰 더 효율적인 기계어 생성
크로스 플랫폼바이트코드는 동일, JIT은 OS별로 번역

JIT이 성능을 대폭 향상시켜서, Java가 인터프리터 언어처럼 보이지만 실제론 매우 빠른 이유

4.5 AOT vs JIT vs 인터프리터

구분설명예시
AOT (Ahead-of-Time)컴파일 타임에 기계어로 번역C, C++, Rust
JIT (Just-in-Time)실행 중에 기계어로 변환Java, .NET
인터프리터줄마다 해석해서 실행Python, JavaScript

4.6 JIT이 없는 언어에서도 최적화는 존재한다?

Python의 표준 구현인 CPython은 JIT이 없지만,
PyPy는 JIT을 도입해 4~10배 빠르기도 함

JavaScript도 V8 엔진이 내부적으로 JIT을 활용해 웹 브라우저 속도를 혁신적으로 개선

4.7 JIT의 단점

단점설명
초기 실행 속도 느림실행 중 컴파일하느라 딜레이 생김
메모리 사용 ↑컴파일된 코드 캐싱 필요
예측 어려움어떤 부분이 최적화될지 상황마다 다름

그래서 JIT은 항상 빠른 게 아니라, 시간이 지날수록 빨라지는 구조

4.8 예시로 흐름 비교

Java 실행 구조

1
2
3
4
5
[.java 소스]  
   ↓ 컴파일  
[.class 바이트코드]  
   ↓ JVM + JIT  
[기계어 실행]

C 실행 구조 (AOT)

1
2
3
4
[.c 소스]  
   ↓ 컴파일  
[실행 파일]  
→ 바로 기계어 실행

✅ 5. 정적 바인딩 vs 동적 바인딩

5.1 바인딩(Binding)이란?

바인딩은 어떤 이름(함수, 변수 등)실제 메모리 위치나 코드와 연결되는 시점을 말한다.

  • 변수 → 메모리 공간에 연결
  • 함수 호출 → 어떤 코드가 실행될지 연결

→ 이 바인딩이 “언제” 일어나는지에 따라 정적 / 동적으로 나뉜다.

5.2 정적 바인딩 (Static Binding)

바인딩이 컴파일 타임에 미리 결정되는 방식이다.

특징

  • 어떤 함수/변수가 실행될지 컴파일할 때 결정됨
  • 빠르다 (미리 정해졌으므로 실행 중 고민할 게 없음)
  • C, C++ 등의 기본 방식

예시 (C)

1
2
3
int add(int a, int b) { return a + b; }

int main() { int result = add(3, 4); // add 함수는 컴파일 타임에 연결됨 }

5.3 동적 바인딩 (Dynamic Binding)

바인딩이 실행 중(Run-Time)에 결정되는 방식이다.

특징

  • 실행 시점에 실제 어떤 함수가 호출될지 결정됨
  • 느리지만 유연함 (다형성, 오버라이딩 등 지원)
  • Java, Python, JavaScript 등에서 자주 사용

예시 (Java)

1
2
3
4
5
class Animal { void sound() { System.out.println("??"); } }

class Dog extends Animal { void sound() { System.out.println("멍멍"); } }

Animal a = new Dog(); a.sound(); // 실행 중에 Dog의 sound()가 호출됨

5.4 비교 표

항목정적 바인딩동적 바인딩
시점컴파일 타임실행 타임
속도빠름느림
유연성낮음높음 (다형성, 오버라이딩 등)
예시 언어C, C++ (기본)Java, Python, JavaScript 등
대표 예일반 함수 호출가상 함수, 오버라이딩 메서드

5.5 동적 바인딩이 필요한 이유

객체 지향 언어에서 “다형성(polymorphism)” 을 구현하려면
동적 바인딩이 반드시 필요하다.

예: Java에서의 오버라이딩

1
Animal a = new Cat(); a.sound(); // Cat의 sound() 실행됨

→ 컴파일 시에는 Animal 타입만 보고 컴파일하지만,
→ 실행 시점에서 진짜 객체(Cat)를 확인해서 바인딩함

5.6 언어별 기본 방식

언어바인딩 방식설명
C정적빠르고 예측 가능, 유연성은 낮음
C++기본 정적 + virtual 키워드로 동적 가능 
Java기본 동적 (클래스 메서드는 대부분 동적) 
Python기본 동적 (모든 속성과 메서드 호출이 런타임 결정) 

5.7 정리하면?

  • 정적 바인딩 → 빠르고 단순, 성능 위주
  • 동적 바인딩 → 유연하고 확장성 ↑, 하지만 약간 느림
  • 객체지향의 핵심인 다형성(Polymorphism)은 동적 바인딩으로 구현된다!

✅ 6. 주요 언어의 런타임 특성

6.1 Java – JVM 기반의 정적 타입 언어

Java는 코드를 바이트코드로 컴파일한 뒤,
JVM(Java Virtual Machine) 이 실행하는 구조

특징

항목설명
실행 구조소스 → 바이트코드(.class) → JVM이 실행
메모리 관리자동 (GC)
실행 최적화JIT 컴파일 지원
타입정적 타입 (컴파일 시 타입 검사)
병행성스레드 기반 병렬 처리, 동기화 키워드 지원

JVM 덕분에 운영체제에 독립적이며, 대규모 서비스에 적합

6.2 Python – 인터프리터 기반 동적 타입 언어

Python은 코드를 한 줄씩 인터프리팅하며 실행되는 구조

특징

항목설명
실행 구조소스(.py) → 바이트코드(.pyc) → CPython이 해석
메모리 관리참조 카운팅 + GC
실행 속도상대적으로 느림 (JIT 없음)
타입동적 타입 (실행 중 타입 결정)
병행성GIL(Global Interpreter Lock) 존재 → 병렬 처리 제약

생산성이 매우 높고 문법이 쉬워서 AI/데이터 분야에서 널리 사용됨

6.3 C – AOT 기반 고성능 시스템 언어

C는 컴파일 시 기계어로 번역된 실행파일을 직접 실행

특징

항목설명
실행 구조소스(.c) → 컴파일러 → 실행파일(.exe)
메모리 관리수동 (malloc/free)
실행 속도매우 빠름 (JIT, 인터프리트 없음)
타입정적 타입
병행성저수준 스레드 API 직접 사용

매우 빠르지만 버그 발생 시 위험 (메모리 해제 누락, 포인터 오류 등)

6.4 JavaScript – 이벤트 기반 인터프리터 언어

JavaScript는 웹 브라우저 또는 Node.js에서
V8 엔진 같은 인터프리터 + JIT 기반 엔진으로 실행됨

특징

항목설명
실행 구조소스 → 인터프리팅 + JIT 최적화
메모리 관리자동 (GC)
타입동적 타입
병행성싱글 스레드 + 이벤트 루프 구조
비동기 처리Promise, async/await

브라우저뿐만 아니라 서버(Node.js)에서도 널리 사용

6.5 언어별 런타임 특성 비교 요약

언어실행 방식타입메모리 관리병행성
JavaJVM + JIT정적GC (자동)스레드
Python인터프리트동적참조 카운트 + GCGIL로 병렬 제약
CAOT 컴파일정적수동 (malloc/free)스레드 직접 구현
JavaScript인터프리트 + JIT동적GC (자동)이벤트 루프 기반

6.6 요약 포인트

  • Java: 안정성과 성능 균형, 대형 서비스에 적합
  • Python: 쉬운 문법, 빠른 개발, 하지만 실행 속도는 느림
  • C: 최상위 성능, 위험도 큼, 시스템 프로그래밍용
  • JavaScript: 웹 친화적, 비동기 처리 강점

언어마다 런타임 구조가 달라서
성능 최적화, 디버깅 방식, 메모리 이슈 대응 방식도 다르다!

This post is licensed under CC BY 4.0 by the author.