

- 자바의 개발 환경으로, 자바 어플리케이션을 개발하기 위해 필요한 도구
- 자바 언어를 바이트코드로 컴파일해주는 자바 컴파일러(javac), 자바 클래스를 해석해주는 어셈블리어(javap) 등이 있다.
- 자바 실행 환경
- JVM, 자바 클래스 라이브러리, 기타 자바 어플리케이션 실행에 필요한 파일들을 포함한다.
- 자바 가상 머신, 자바 어플리케이션을 실행하는 가상머신
- 실제 컴퓨터로부터 JAVA 어플리케이션 실행을 위한 메모리를 할당받아 Runtime Data Are를 구성
- 인터프리터(Interpreter)와 JIT 컴파일러를 통해 바이트코드를 각 운영체제에 맞는 기계어로 해석시켜 실행시키고, 가비지 컬렉터를 통해 어플리케이션의 동적 메모리를 관리한다.
JVM 내로 클래스 파일 (*.class)을 동적으로 로드하고, 링크를 통해 배치하는 작업을 수행하는 모듈이다. 즉, 로드된 바이트 코드(.class)를 엮어서 JVM의 메모리 영역인 Runtime Data Area에 배치한다.
Loading : 클래스 파일을 가져와 JVM의 메모리에 로드한다.
Linking : 클래스 파일을 사용하기위해 검증하는 과정
Initializing : 클래스 변수들을 적절한 값으로 초기화한다. (static 필드들을 설정된 값으로 초기화 등)
클래스 로더를 통해 런타임 영역에 배치된 바이트코드를 명령어 단위로 읽어서 실행한다. 자바 바이트 코드 (*.class)는 기계가 바로 실행할 수 있는 언어가 아니라 가상머신이 이해할 수 있는 중간 레벨로 컴파일된 코드이다. 그래서 실행엔진은 이와 같은 바이트코드를 실제로 JVM 내부에서 기계가 실행할 수 있는 형태로 변경한다. 인터프리터 + JIT 컴파일러 두 방식을 혼합하여 바이트코드를 실행한다.
바이트 코드 명령어를 하나씩 읽어서 해석하고 바로 실행한다.
Interpreter의 단점을 보완하기위한 방법으로, 바이트 코드 전체를 컴파일하여 Native Code로 변경하고 이후에는 더이상 인터프리팅하여 실행하지 않고 캐싱해 두었다가 네이티브 코드로 직접 실행하는 것이다.
하나씩 인터프리팅하여 실행하지 않고, 컴파일된 네이티브 코드를 실행하는 것 => 전체적인 속도는 인터프리팅 방식보다 빠르다.
바이트코드에서 Native Code로 바꾸는 비용도 발생하기에 모든 코드를 JIT 방식으로 해석하지 않는다. 사용하다가 일정 기준을 넘어가면 JIT 방식을 사용한다.
일정 기준 : Client 모드랑 Server 모드가 있는데, Client 모드에서는 150번, Server 모드에서는 10000번이라고 한다. 그리고 이 기준은 변경할 수 있다고 한다.
Native Code : Java에서 부모가 되는 C언어나, C++, 어셈블리어로 구성된 코드
Heap 메모리 영역에서 더는 사용하지 않는 메모리를 자동으로 회수해준다.
System.gc()라는 메소드를 사용할 수 있지만, 함수 실제 실행은 보장되지 않는다.JVM의 데이터 영역으로 자바 어플리케이션을 실행할 때 사용되는 데이터들을 적재하는 영역
JVM이 시작될 때 생성되는 공간으로, 바이트 코드(.class)를 처음 메모리 공간에 올릴 때 초기화하는 대상을 저장하기 위한 메모리 공간으로 Class Area나 Static Area로도 불리운다.
- Field Info : 멤버 변수의 이름, 데이터 타입, 접근 제어자 정보
- Method Info : 메소드 이름, return type, 함수 매개변수, 접근 제어자 정도
- Type Info : Class인지, Inteface인지 여부 저장, Type 속성 이름, Super class의 이름
간단히 말하자면, 메서드 영역에는 정적 필드와 클래스 구조만 갖고 있다.
- 메서드 영역에 존재하는 별도의 관리 영역
- 각 클래스/인터페이스마다 별도의 Constant Pool 테이블이 존재하는데, 클래스를 생성할 때 참조해야할 정보들을 상수로 가지고 있는 영역이다.
- JVM은 이 상수풀을 통해 해당 메소드나 필드의 실제 메모리상 주소를 찾아 참조한다.
정리하면, 상수 자료형을 저장하여 참조하고 중복을 막는 역할을 한다.
- JVM 시작 시 생성
- 명시적으로 null 선언 시
- 프로그램 종료 시까지
메서드 영역과 함께 모든 스레드가 공유하며, JVM이 관리하는 프로그램 상에서 데이터를 저장하기위해 런타임 시 동적으로 할당하여 사용하는 영역이다. 즉,
new연산자로 생성되는 클래스와 인스턴스 변수, 배열 타입 등 Reference Type이 저장되는 곳
힙 영역에 생성된 객체와 배열은 Reference Type, JVM의 스택 영역의 변수나 다른 객체의 필드에서 참조된다.
즉, 힙의 참조 주소는 "스택"이 가지고 있고, 해당 객체를 통해서만 힙 영역에 있는 인스턴스를 핸들링할 수 있는 것이다.
만약, 참조하는 변수나 필드가 없다면 의미없는 객체가 되기 때문에 쓰레기로 취급한다. JVM은 GC를 실행시켜 쓰레기 객체를 힙 영역에서 자동으로 제거한다. 즉, 힙 영역은 GC의 대상이 되는 공간이다.
- 객체가 더 이상 사용되지 않거나 명시적으로 null 선언 시
- GC의 대상
int,boolean,long등과 같은 기본 참조형을 생성할 때 저장하는 공간으로 사용되는 변수나 정보들이 저장되는 영역이다.
메서드 호출 시마다 각각의 스택 프레임 (그 메서드를 위한 공간)이 생성되고, 메서드 안에서 사용되는 값들을 저장하고 호출된 메서드의 매개변수, 지역변수, 리턴 값 및 연산 시 일어나는 값들을 임시로 저장한다. 그리고 메서드 수행이 끝나면 자동으로 삭제된다.
메서드가 호출될 때마다 프레임이 만들어지며, 현재 실행중인 메서드 상태 정보를 저장하는 곳이다. 메서드 호출 범위가 종료되면 스택에서 삭제된다. 스택 프레임에 쌓이는 데이터는 메서드의 매개변수, 지역변수, 리턴 값 ,연산 시 결과값등이 있다.
각 스레드마다 하나씩 존재하며 스레드가 존재될 때 할당된다. 프로세스가 메모리에 로드될 때, 스택 사이즈가 고정되어있어 런타임 시에 스택 사이즈를 바꿀 수 없다. 메모리의 크기가 충분하지 않다면, StackOverFlowError 가 발생한다. 스레드 종료 시 런타임 스택도 사라진다.
Person p = new Person();
p : 생성된 클래스의 참조, Stack에 저장new Person() : Heap에 저장스레드가 시작될 때 생성되며, 현재 수행중인 JVM 명령어 주소를 저장하는 공간이다. JVM 명령어 주소는 스레드가 어떤 부분을 무슨 명령으로 실행해야할지에 대한 기록을 가지고 있다.
일반적인 레지스터는 CPU내의 기억장치로, JAVA의 PC Register과 다르다. 자바는 OS나 CPU 입장에서는 하나의 프로세스이기 때문에 가상머신인 JVM의 리소스를 이용해야 한다. 그래서 자바는 CPU에 직접 연산을 수행하도록 하는 것이 아닌, 현재 작업하는 내용을 CPU에게 연산으로 제공해야 하며, 이를 위한 버퍼 공간으로 PC Register라는 메모리 영역을 만들게 된 것이다.
따라서, JVM 스택에서 비연산값 Operand를 뽑아 별도의 메모리 공간인 PC Register에 저장하는 방식을 취한다.
만약, 스레드가 자바 메소드를 실행 중일 땐, JVM 명령어 주소를 PC Register에 저장한다.
만약, 자바가 아닌 다른 언어 (C, 어셈블리어)의 메소드를 실행 중일 땐, undefined로 남긴다.
이는 두 경우를 따로 처리하기 때문이다.
바이트 코드가 아닌 기계어로 작성된 프로그램을 실행시키는 영역이다. 또한, 자바 이외의 (C, C++, 어셈블리 등) 언어로 작성된 네이티브 코드를 실행하기 위한 공간이다.
사용되는 메모리 영역으로는 일반적인 C스택을 사용한다.
자바가 다른 언어로 만들어진 어플리케이션과 상호 작용할 수 있는 인터페이스를 제공하는 프로그램
GC는 가비지 콜렉터 (Garbage Collector)로, 자바의 메모리 관리 기법 중 하나이다. JVM의 Heap 영역에서 동적으로 할당했던 메모리 중 필요없게된 객체(Garbage)를 모아 주기적으로 제거하는 프로세스를 말한다.
GC가 필요한 이유
C/C++ 언어에서는 GC가 없어 개발자가 수동으로 메모리 할당과 해제를 일일이 해야 한다. 반면, JAVA는 GC가 메모리 관리를 대행해주기 때문에 Java 프로세스가 한정된 메모리를 효율적으로 사용할 수 있게 한다. 개발자 입장에서 메모리 관리, 메모리 누수 문제에 대해 관리하지 않아도 되며, 개발에만 집중할 수 있게 해준다.
단점
1. 자동으로 처리한다해도 메모리가 언제 해제되는지 정확하게 알 수 없어 제어하기 힘들다.
2. GC가 동작하는 동안에는 다른 동작을 멈추기 때문에 오버헤드가 발생되는 문제점이 있다.
GC가 수행되는동안 JVM이 프로그램 실행을 멈추는 현상을 의미
GC가 작동하는동안 GC 관련 Thread를 제외한 모든 Thread는 멈추게 되어 서비스 이용에 차질이 생길 수 있다. 따라서, 이 시간을 최소화하는 것이 중요하다.
가비지 컬렉션은 특정 객체가 garbage인지 아닌지 판단하기 위해 도달성, 도달능력(Reachability)의 개념을 적용한다.
객체에 레퍼런스가 있다면 Reachable로 구분되고, 객체에 유효한 레퍼런스가 없다면 Unreachable로 구분해버리고 수거해버린다.
Reachable : 객체가 참조되고 있는 상태
Unreachable : 객체가 참조되고 있지 않은 상태 (GC의 대상이 됨)
- Heap Area 객체의 메모리 주소를 가지고 있는 참조 변수가 삭제되는 현상이 발생하면, Heap 영역에서 어디서든 참조하고 있지 않는 객체(Unreachable)들이 발생하게 된다.
다양한 GC에서 사용되는 객체를 솎아내는 내부 알고리즘이다. 가비지 컬렉션이 동작하는 아주 기초적인 청소 과정이다.
가비지 컬렉션이 될 대상 객체를 Mark(식별)하고 Sweep(제거)하며 객체가 제거되어 파편화된 메모리 영역을 앞에서부터 채워나가는 작업(Compaction)을 진행하게 된다.
Mark 과정 : 먼저 Root Space로부터 그래프 순회를 통해 연결된 객체들을 찾아내어 각각 어떤 객체를 참조하고 있는지 찾아서 마킹한다.
Sweep 과정 : 참조하고 있지 않은 객체 즉, Unreachable 객체들을 Heap에서 제거한다.
Compact 과정 : Sweep 후에 분산된 객체들을 Heap의 시작 주소로 모아 메모리가 할당된 부분과 그렇지 않은 부분으로 압축한다.(가비지 컬렉터 종류에 따라 하지 않는 경우도 있다.)
이렇게 Mark-Sweep 방식을 사용하면 루트로부터 연결이 끊긴 순환 참조되는 객체들을 모두 지울 수 있다.
객체는 대부분 일회성이며, 메모리에 오랫동안 남아있는 경우가 드물기 때문에 보다 효율적인 메머리 관리를 위해 객체의 생존 기간에 따라 물리적인 Heap 영역을 Young과 Old 총 2가지 영역으로 설계하였다.
Heap 영역이 설계될 때의 2가지 전제
1. 대부분의 객체는 금방 접근 불가능한 상태(Unreachable)가 된다.
2. 오래된 객체에서 새로운 객체로의 참조는 아주 적게 존재한다.
- 새롭게 생성된 객체가 할당되는 영역
- 대부분의 객체가 금방 Unreachable 상태가 되기 때문에, 많은 객체가 Young 영역에 생성되었다가 사라진다.
- Young 영역에 대한 가비지 컬렉션을 Minor GC라고 부른다.
또 다시 힙 영역은 효율적인 GC를 위해 Young 영역을 3가지 영역(Eden, survivor0, survivor1)으로 나눈다.
new를 통해 새로 생성된 객체가 위치- 정기적인 쓰레기 수집 후 살아남은 객체들은 Survivor 영역으로 보낸다.
- 최소 1번 이상의 GC를 살아남은 객체가 존재하는 영역
- Young 영역에서 Reachable 상태를 유지하여 살아남은 객체가 복사되는 영역
- Young 영역보다 크게 할당되며, 영역의 크기가 큰만큼 가비지는 적게 발생한다.
- Old 영역에 대한 가비지 컬렉션을 Major GC 또는 Full GC라고 부른다.
Young Generation 영역에서 발생되는 GC를 Minor GC라 부른다. Young Generation의 공간은 Old Generation에 비해 상대적으로 작기 때문에 메모리 상의 객체를 찾아 제거하는 데에는 적은 시간이 걸린다.
age 값이 1씩 증가age가 1씩 증가Survivor 영역에서 객체가 살아남은 횟수를 의미하는 값이며, Object Header에 기록된다. 만일 age 값이 임계값에 다다르면 Promotion(Old 영역으로 이동) 여부를 결정한다.
Major GC는 객체들이 계속 Promotion 되어 Old 영역의 메모리가 부족해지면 발생하게 된다.
age가 임계값에 도달하게 된다.Old Generation은 Young Generation에 비해 상대적으로 큰 공간을 가지고 있어, 이 공간에서 메모리상의 객체 제거에 많은 시간이 걸리게 된다.
예를 들어 Young 영역은 일반적으로 Old 영역보다 크기가 작기 때문에 보통 0.5초에서 1초 사이에 끝난다. 그렇기 때문에 Minor GC는 애플리케이션에 크게 영향을 주지 않는다.
하지만, Old 영역의 Major GC는 일반적으로 Minor GC보다 시간이 오래걸리며, 10배 이상의 시간을 사용하고, 여기서 Stop-The-World 문제가 발생하게 된다.
추상화, 캡슐화, 상속, 다형성
- 객체들이 공통적으로 필요한 속성이나 동작을 하나로 추출하는 작업
- 불필요한 부분은 숨기고, 중요한 부분에 중점을 두어 개략화하는 것
구현
추상 클래스 (abstract class) 와 interface로 구현할 수 있다.
기능
공통된 기능을 다시 정의할 필요가 없기에 코드 중복이 줄고, 코드 재사용이 용이해진다.
- 자식 클래스와 부모 클래스를 두어 자식 클래스가 부모 클래스의 속성과 메서드를 물려받는 것
- 기존 클래스를 재활용하여 새로운 클래스를 작성하는 것
구현
1. extends 키워드를 통하여 부모 클래스를 상속받을 수 있다.
2. super 을 통해 부모 클래스를 참조하거나 호출할 수 있다.
기능
- 서로 연관있는 속성과 기능들을 하나의 캡슐로 만들어 외부로부터 데이터를 보호하는 것
- 외부로부터 데이터를 보호하기위해 접근에 제한을 둔다.
구현
1. 접근제어자로 구현할 수 있다.
2. Getter/Setter : 접근 경로를 제한한다는 점에 캡슐화의 방법, 개발자가 여러 명일 수 있는데, 그냥 public으로 풀어져있으면 다른 개발자가 그냥 접근할 수도 있다. 그걸 방지할 수 있기에 캡슐화의 방법 !
또, Getter/Setter를 어디서 함수 호출을 하는지 호출할 수 있다. 그럼 값이 잘못 수정되거나 접근 됐을 때 추적도 가능하다. => 유지/보수에 도움이 된다.
기능
1. 데이터 보호 : 외부로부터 클래스에 정의된 속성과 기능들을 보호
2. 데이터 은닉 : 내부의 동작을 감추고 외부에는 필요한 부분 노출
- 어떤 객체의 속성이나 기능이 상황에 따라 여러 가지 형태를 가질 수 있는 성질
- 그 맥락에 따라 다른 역할을 수행할 수 있는 특징
구현
1. 메서드 오버로딩 (Method Overloading) : 동일한 함수 이름으로 다른 매개변수 타입, 개수를 받으며 구현할 수 있다.
2. 메서드 오버라이딩 (Method Overriding) : 함수의 구현체를 상속클래스나 구현체에서 재정의할 수 있도록 하는 것이다.
저장 공간의 차이
1. String a = "abcd"; :
저장 공간
같은 값의 객체가 존재하는 경우
2. String b = new String("abcd");
저장공간
new라는 생성자를 통해 생성했기 때문에 Heap 영역에 저장된다.
같은 값의 객체가 존재하는 경우
new 연산자로 생성한 String 객체는 같은 값이 String pool에 존재하더라도, Heap영역에 별도로 객체를 생성한다.성능 비교
문자열 리터럴을 사용하는 것이 new String()을 사용하는 것보다 성능이 더 좋다.
String str = new String("Hello World!").intern(); // String pool에 강제로 넣음.
intern()은 문자열 상수 풀에 해당 문자열이 있으면 기존 상수풀에 있는 레퍼런스 값을 반환하고, 없으면 새롭게 문자열 상수 풀에 저장한 후 레퍼런스를 반환한다.