🪕 컴파일 과정이란?
컴파일: 소스를 기계어로 번역
컴파일러: 고급언어로 작성된 프로그램을 기계어로 번역하는 것
- 개발자가 고급언어로 프로그래밍한 코드를 CPU가 이해할 수 있게 변환해주는 것
빌드: 컴파일을 포함해 프로그램을 실행 가능한 파일로 만들어주는 과정
고급언어 > 어셈블리어 > 기계어 > 실행파일 > 실행
- 컴파일러가 고급언어를 어셈블리어 언어로 번역
- 어셈블러가 어셈블리 언어를 기계어로 번역
- 링커가 어셈블러에 의해 생성된 객체코드를 결합하여 실행 가능한 모듈 생성
- 로더가 링커에서 생성된 실행 가능한 모듈(파일)을 메인 메모리에 로드하여 실행
🪕 링커 vs. 로더
링커:
- 어셈블러가 생성한 소스 프로그램의 객체 코드를 받아 프로그램의 실행 코드 생성
- 프로그램에 내장 라이브러리 함수가 있으면 내장 라이브러리에 연결
- 내장 라이브러리가 발견되지 않으면 컴파일러에 알림 > 컴파일러는 오류 생성함
- 모든 개체 모듈을 결합/연결하여 원본 프로그램의 단일 실행 파일 생성
- 모듈: 큰 프로그램은 때때로 모듈이라고 하는 서브 프로그램으로 나뉨
- 두 가지 유형:
- 링키지 편집기: 재배치 가능한 실행 가능 모듈 생성
- 다이나믹 링커: 로드 모듈/실행 모듈이 생성될때까지 일부 외부 모듈의 연결을 연기
로더:
- 운영체제의 프로그램
- 기능:
- 할당, allocation: 실행 프로그램을 실행시키기 위해 기억장치 내에 옮겨놓을 공간을 확보
- 연결, linking: 부 프로그램 호출 시 그 부 프로그램이 할당된 기억장소의 시작주소를 호출한 부분에 등록하여 연결
- 재배치, relocation: 디스크 등의 보조기억장치에 저장된 프로그램이 사용하는 각 주소들을 할당된 기억장소의 실제 주소로 배치
- 적재, loading: 실행 프로그램을 할당된 기억공간에 실제로 옮김
- 로더의 종류:
- Compile and Go 로더:
- 별도의 로더 없이 언어 번역 프로그램이 로더의 기능까지 수행
- 연결 기능 수행 X
- 할당, 재배치, 적재 작업 담당
- 절대로더:
- 목적 프로그램을 기억장소에 적재시키는 기능만 수행
- 매번 같은 주 메모리 위치에 프로그램의 실행파일 로드
- 단점: 프로그램에서 일부 삽입/삭제 등 프로그램을 수정해야 하는 경우 프로그램의 모든 주소 변경해야 함
- 직접 연결 로더
- 로드의 기본 기능 네가지 모두 수행
- aka 재배치 로더, 상대 로더
- 동적 적재 로더:
- 매우 유연
- 프로그램에 한꺼번 적재 X. 실행시 필요한 부분만 적재
- 실행중인 프로그램은 중간에 중단되거나 디스크로 스왑 아웃 될 수도
- 프로그램의 크기가 주기억장치의 크기보다 큰 경우 유리
🪕 컴파일 언어와 인터프리터 언어
컴파일 언어: 프로그래머가 작성한 소스코드를 모두 기계어로 변환 후 기계에 넣어 기계어 코드 실행
(예) C, C++
- 소스코드를 기계어로 번역하는 빌드 과정 존재
- 빌드 과정: 소스파일을 실행파일로 생성하는 과정
- 런타임에는 이미 기계어로 모든 소스코드가 변환되어 있어 빠르게 실행 가능
인터프리터 언어: 프로그래머가 작성한 소스코드를 기계어로 변환하는 과정없이 한줄씩 해석하여 바로 명령어를 실행하는 언어
(예) R, Python, Ruby
- 인터프리터가 직접 한줄씩 읽고 따로 기계어로 변환하지 않음 > 빌드 시간 없음
- 런타임에 실시간으로 읽어서 실행 > 컴파일언어에 비해 속도 느림
- 빌드 과정이 없음 > 서버를 다시 시작하지 않아도 변경사항 반영
🪕 Just in Time (JIT)
Just In Time 컴파일: 프로그램을 실행하는 시점에서 필요한 부분을 즉석으로 컴파일
- 같은 코드를 매번 해석하는 대신 처음 실행될 때 인터프리트 하면서 자주 쓰이는 코드 캐싱
- 이후에는 캐싱된 코드 가져다 씀 > 인터프리터의 느린 실행 속도 개선
- 고속 코드 실행 및 다중 플랫폼 지원을 위해 설계
- 단점:
- 초기 구동 시에는 소스코드를 실행 단계에서 컴파일하는데 시간 & 메모리 소모
- 정적 컴파일된 프로그램에 비해 실행 속도 느림
- 캐시 메모리 사용 증가
- 여러 프로세스에서 코드 공유 불가
- 보안 문제:
- 런타임에 동적으로 코드 생성 & 실행
- JIT 컴파일러의 버그는 보안취약점으로 직결
🪕 Java의 컴파일과 실행 과정
- 개발자가 자바 소스코드(.java) 작성
- 자바 컴파일러가 자바 소스파일 컴파일
- 자바 바이트코드(.class)파일 생성됨
- 컴퓨터는 읽을 수 없는 코드
- 자바 가상 머신은 이해할 수 있는 코드
- 바이트 코드의 각 명령어는 1바이트 크기의 Opcode와 추가 피연산자(Operand)로 이루어져있음
- 컴파일된 바이트 코드를 JVM의 클래스로더(Class Loader)에게 전달
- 클래스 로더는 동적 로딩을 통해 필요한 클래스들을 로딩 및 링크하여 런타임 데이터 영역(=JVM의 메모리)에 올림
- 클래스 로더 세부 동작
- 로드: 클래스 파일을 가져와서 JVM의 메모리에 로드
- 검증: 자바 언어 명세 및 JVM 명세에 명시된 대로 구성되어 있는지 검사
- 준비: 클래스가 필요로 하는 메모리 할당
- 분석: 클래스의 상수 풀 내 모든 심볼릭 레퍼런스를 다이렉트 레퍼런스로 변경
- 초기화: 클래스 변수를 적절한 값으로 초기화
- 실행 엔진은 JVM 메모리에 올라온 바이트 코드를 명령어 단위로 하나씩 가져와 실행
- 인터프리터: 바이트 코드 명령어를 하나씩 읽어서 해석하고 실행
- 하나하나의 실행은 빠르나 전체적 실행 속도가 느림
- JIT 컴파일러:
- 인터프리터의 단점 보완을 위해 도입
- 바이트 코드 전체를 컴파일하여 바이너리 코드로 변경 > 해당 메서드를 더이상 인터프리팅하지 않고, 바이너리 코드로 직접 실행
- 하나씩 인터프리팅하여 실행하지 않고 바이트 코드 전체가 컴파일된 바이너리 코드 실행
- 전체적인 실행 속도는 인터프리팅보다 빠름
참고: