1. [JAVA의 바이트 코드는 운영체제에 맞게 명령어를 변환해주는 JVM으로 인해 플랫폼(CPU 및 운영체제) 독립적이다]
JAVA는 C/C++과 달리 하드웨어, 운영체제 상관없이 컴파일된 코드(바이트 코드)가 플랫폼 독립적이다.
그이유는 JVM이라는 가상머신이 사용되기때문이다.
JVM이 각 운영체제에 맞게 명령어를 변환해주기때문에 JAVA 바이트 코드는 플랫폼 독립적이다.
차이)
JAVA->컴파일->바이트 코드-> JVM->모든 플랫폼 가능
<-> 반대로,
C/C++->컴파일->네이티브코드 ->해당 플랫폼(CPU 및 운영체제)
2. [JAVA 컴파일 과정]
① 개발자가 .java 코드 작성
② 자바 컴파일러(javac)가 자바 소스 컴파일해서 *바이트 코드(.class)로 변환
③ 바이트 코드를 JVM의 클래스 로더에게 전달
④ 클래스 로더는 *동적 로딩을 통해 필요한 *클래스들을 로드하고 링크하여 런타임 데이터 영역(=JVM 메모리)에 올림.
⑤ 실행엔진(Execution Engine)은 JVM에 올라온 바이트 코드들을 명령어 단위로 하나씩 가져와서 실행함.
이떄, *두 가지 방식(인터프리터, JIT컴파일러)으로 기계가 실행할 수 있는 방식으로 변경
*바이트 코드: JVM이 이해할 수 있는 코드 (아직 CPU가 이해할 수 있는 기계어는 아님)
각 명령어는 1바이트 크기의 Opcode와 추가 피연산자로 구성됨
예시)
// Compiled from HelloWorld.java (version 1.8 : 52.0, super bit)
public class HelloWorld {
public HelloWorld();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
public static void main(java.lang.String[]);
Code:
0: getstatic #7 // Field java/lang/System.out:Ljava/io/PrintStream;
3: ldc #13 // String Hello, World!
5: invokevirtual #15 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
8: return
}
* 동적 로딩: 필요할 때마다 로딩하는 방식으로, JDBC에서 DB 연결시와 같이 변경이 용이하고, 메모리 효율성을 높일 수 있다는 장점이 있으나. *오버헤드(추가 시간 소요)가 발생할 수 있다. <-> 정적 로딩은 처음부터 다 로딩하는 방식이다. 고정된 환경에서 적합하다.
* 오버헤드: 작업 자체에 필요한 작업 외 작업을 관리하기 위해 필요한 추가 리소스(시간,메모리,처리능력)을 의미. 즉, 추가비용
* 클래스 로드 및 링크 과정
- 로드: 클래스 파일을 가져와서 JVM 메모리에 로드
- 검증: 자바 언어 명세 및 JVM 명세에 명시된 대로 구성되어 있는지 검사
- 준비: 클래스가 필요로 하는 메모리 할당. (필드, 메서드, 인터페이스 등)
- 분석: 클래스 상수 풀 내의 모든 심볼릭 레퍼런스(필드, 메서드, 인터페이스 등)를 다이렉트 레퍼런스(실제 메모리 주소)로 변환
- 초기화: 클래스 변수들을 적절한 값으로 초기화 (static 필드)
*두 가지 방식(인터프리터, JIT컴파일러)
- 인터프리터: 바이트 코드 하나하나 읽고 실행. JVM 내 바이트 코드는 기본적으로 인터프리터 방식으로 동작. 그러나 느리다.
- JIT컴파일러(Just-In-Time Compiler): 인터프리터 단점 보완을 위해 도입됨. 전체 바이트 코드 -> 컴파일 -> 네이티브 코드 로 변경하여 직접 실행하는 방식이다. 하나하나 인터프리팅 할 필요없어서 빠르다.
3. [JVM의 기능은 첫번째. 플랫폼 독립성 유지, 두번째. 메모리 관리이다]
- 플랫폼 독립성 유지: JVM이 각 플랫폼에 맞는 기계어로 변환
- 메모리 관리: Java 프로그램이 사용하는 메모리 관리를 담당. 특히, 가비지 컬렉터(GC)를 통해 더이상 참조되지 않는 객체를 회수해서 메모리를 자동 관리 *(주로 힙, 일부 메소드 영역-클래스 언로딩)
주요 메모리 관리 영역: 힙, 스택, 메소드 영역
* 가비지 컬렉터(GC)가 관리하는 영역은 주로 힙이고, 일부 메소드 영역에서 사용되지 않는 클래스 메타데이터를 정리할 수 있다(클래스 언로딩).
*스택은 가비지 컬렉터가 관리하지 않는데 그이유는 스택 메모리가 각 스레드에서 로컬 변수와 메서드 호출을 저장하는 영역이므로, 메서드가 완료되면 자동으로 정리된다. 즉, 메서드 호출 스택에 의해 이루어지기때문이다.
4. [런타임 데이터 영역(=JVM 메모리)- 5가지]
- (스레드 별) *PC 레지스터: 스레드가 어떤 명령어로 실행 될지 기록하는 부분(JVM 명령의 주소를 가짐). 현재 수행 중인 명령의 주소를 가지며 스레드가 시작될 때 마다 생성된다. 각 스레드마다 하나씩 존재함
- (스레드 별) JVM Stack: 지역변수, 매개변수, 메서드 실행 관련 임시데이터 등을 저장. 메서드가 호출될 때마다 스택 프레임(Stack Frame)이라는 구조체를 저장하는 스택이다. 예외 발생 시 *printStackTrace()메서드로 보여주는 Stack Trace의 각 라인 하나가 스택 프레임을 표현한다. JVM Stack도 PC 레지스터 처럼 스레드 마다 하나씩 존재한다.
- (스레드 별) Native Method Stack: 실제 실행할 수 있는 기계어로 작성된 프로그램을 실행시키는 영역. JAVA 외의 언어로 작성된 네이티브 코드를 위한 스택이다. *JNI(JAVA Native Interface)를 통해 호출되는 C/C**등의 코드를 수행하기 위한 스택으로, 언어에 맞게 스택이 생성됨 (ex. C면 C스택, C++이면 C++스택)
- (공유 자원) Heap: *인스턴스 또는 객체가 저장되는 영역. 인스턴스 변수 저장. GC의 대상으로, JVM 성능이슈에서 가장 많이 언급되는 영역. 힙구성 방식이나 *가비지 컬렉션 방식은 JVM 벤더들의 재량임.
- (공유 자원) Method Area: 클래스에 대한 메타데이터(클래스 및 인터페이스에 대한 런타임 상수풀, 메서드 코드(바이트코드)) 저장. Static 변수(클래스 변수) 보관. JVM이 시작될 때 생성됨.
- 런타임 상수 풀(Runtime Constant Pool): JVM에서 가장 핵심적인 역할을 수행하는 곳. 각 클래스와 인터페이스의 상수 뿐 아니라 , 메서드와 필드에 대한 모든 레퍼런스까지 담고 있는 테이블. 메서드나 필드(멤버변수)를 참조할 때 여기서 실제 메모리 주소를 찾아서 참조함.
*PC 레지스터: JVM이 아래와 같은 바이트 코드를 명령어 단위로 실행할때 현재 실행중인 명령어의 메모리의 주소
ex) 프로그램이 시작하면 PC 레지스터는 0을 가리키고, iconst_10 명령을 실행
0: iconst_10 // 명령어 1: 정수 10을 스택에 로드
1: istore_1 // 명령어 2: 변수 a에 값 저장
2: iconst_20 // 명령어 3: 정수 20을 스택에 로드
3: istore_2 // 명령어 4: 변수 b에 값 저장
4: iload_1 // 명령어 5: 변수 a의 값을 스택에 로드
5: iload_2 // 명령어 6: 변수 b의 값을 스택에 로드
6: iadd // 명령어 7: 두 정수를 더함
7: istore_3 // 명령어 8: 변수 c에 결과 저장
8: getstatic // 명령어 9: System.out 가져오기
11: iload_3 // 명령어 10: 변수 c의 값을 스택에 로드
12: invokevirtual // 명령어 11: println 호출
15: return // 명령어 12: 메서드 종료
*printStackTrace() 예시:
java.lang.RuntimeException: Error in method2
at StackTraceExample.method2(StackTraceExample.java:16)
at StackTraceExample.method1(StackTraceExample.java:12)
at StackTraceExample.main(StackTraceExample.java:6)
*JNI(JAVA NAtive Interface) 역할:
- 네이티브 코드( C/C++/어셈블리어) 호출
- 성능 향상: 성능이 중요할 경우 네이티브 코드를 사용하여 특정 작업의 성능 향상 가능
- 플랫폼 종속적인 기능 사용: Java는 기본적으로 플랫폼 독립적이지만, 특정 운영체제의 종속적인 기능(ex. 파일 시스템 제어, 하드웨어 제어 등)에 접근하려면 JNI를 통해 네이티브 API를 호출해야 함
- 기존 코드 재사용: 이미 C/C++로 작성된 기존 라이브러리나 시스템 재사용 가능.
*인스턴스 와 객체 차이
Car myCar = new Car();
- 인스턴스: 특정 클래스의 구체적인 객체, 인스턴스 ⊂ 객체. ex. myCar
- 객체: 소프트웨어 내에서 메모리에 저장된 실체 ex. Null 객체, Wrapper Class
*가비지 컬렉션 방식은 JVM 벤더들의 재량
JVM 벤더 =. JVM 공급업체
대표적인 JVM 공급업체: Oracle, OpenJDK, Amazon Corretto, Adoptium, Azul, IBM Semeru, GraalVM
ex. Amazon Corretto
- 장점: OpenJDK 기반의 무료 JVM으로, **장기 지원(LTS)**이 포함되어 있어 AWS 클라우드와의 최적화된 사용이 가능합니다. AWS 사용자들에게 인기 있습니다.
- 단점: AWS 외의 환경에서의 최적화 수준은 다른 JVM에 비해 특화되어 있지 않을 수 있습니다.
- 적합한 환경: AWS 클라우드에서 Java 애플리케이션을 구동하는 사용자.
*인스턴스 변수, Static 변수(클래스 변수), 지역변수 및 매개변수 저장 영역
구분 | 선언 위치 | 초기화 | 접근 | 메모리 영역 | 생명 주기 | |
멤버변수 | 인스턴스 변수 | 클래스 블록 내 | 자동 | 클래스 내부 모든 메서드애서 사용 가능 |
Heap | 객체와 함께 생성 및 소멸 |
클래스 변수 (Static) |
Method Area | 프로그램과 함께 생성 및 소멸 객체 생성없이 사용 |
||||
지역변수 | 메서드 블록 내 | 수동 | 선언된 블록 내부에서만 | Stack | 메서드와 함께 생성 및 소멸 |
5. [가비지 컬렉션(Garbage Collection)
자바 이전에는 프로그래머가 모든 프로그램 메모리를 관리했음 하지만, 자바에서는 JVM이 프로그램 메모리를 관리함!
JVM은 가비지 컬렉션이라는 프로세스를 통해 메모리를 관리함. 가비지 컬렉션은 자바 프로그램에서 사용되지 않는 메모리를 지속적으로 찾아내서 제거하는 역할을 함.
실행순서 : 참조되지 않은 객체들을 탐색 후 삭제 → 삭제된 객체의 메모리 반환 → 힙 메모리 재사용
'백앤드 > JAVA' 카테고리의 다른 글
[JAVA] ==는 주소 값, equals는 값 비교 (0) | 2024.10.18 |
---|---|
[JAVA] 객체지향 프로그래밍 (OOP,Object Oriented Programming) (0) | 2024.10.18 |
[JAVA] String, StringBuilder, StringBuffer 차이 (0) | 2024.10.18 |
[JAVA] 자바는 Call by Value방식이다 (0) | 2024.10.18 |
[JAVA] 입출력 포맷 (0) | 2023.07.17 |