JAVA
< JVM 이란? >
Java Virtual Machine
JVM 의 역할은, 자바 애플리케이션을 (클래스 로더를 통해 읽어 들여 자바 API와 함께) 실행하는 것
JVM 은 JAVA와 OS사이에서 중개자 역할을 수행하여 JAVA 애플리케이션을 어떤 OS 위에서도 동작할 수 있도록 함
JVM 은 자바 언어 자체를 이해하고 있지는 않고, 단지 .class 파일(바이트 코드)을 가져다가 JVM 이 동작중인 OS에 특화된 기계어 코드로 변경하여 실행하는 역할
JVM 은 가장 중요한 메모리관리, Garbage collection 을 수행함
JVM 은 자바 이외에도 여러 언어(그루비, Scala, Kotlin 등)의 바이트 코드를 실행 가능
JVM 은 스택기반의 가상머신임
Java 애플리케이션을 동작하기 위해 부여된 한정된 메모리를 효율적으로 사용하여 최고의 성능을 내기 위해서 JVM 에 대해 알아야 함
메모리 효율성을 높이려면 메모리 구조를 알아야 함
왜냐하면 동일한 기능의 프로그램이더라도 메모리 관리에 따라 성능이 좌우되기 때문
(메모리 관리가 되지 않은 경우, 속도 저하 현상이나 튕김 현상 등이 일어날 수 있음)
< JAVA 애플리케이션 실행 방법 >
1. JAVA 프로그램이 실행되면 JVM은 OS로부터 JAVA 프로그램이 필요로 하는 메모리를 할당받음
(JVM은 이 메모리를 용도에 따라 여러 영역으로 나누어 관리하게 됨)
2. 자바 컴파일러(javac)가 자바 소스코드(.java)를 읽어들여 자바 바이트코드(.class)로 변환
3. Class Loader를 통해 class 파일들을 JVM으로 로딩함
4. 로딩된 class 파일들은 Execution engine을 통해 해석됨
5. 해석된 바이트코드는 Runtime Data Areas 에 배치되고, 실질적인 수행을 진행
이러한 실행과정 속에서 JVM은 필요에 따라 Thread Synchronization 과 GC같은 관리작업을 수행함
< JVM 구성 >
- Class Loader (클래스 로더)
Runtime 에 JVM 내로 class 파일을 동적으로 로드하고, 링크를 통해 배치하는 작업을 수행하는 모듈임
Class Loader 가 class 파일을 로드하는 시점은 컴파일 타임이 아니라 Runtime 임
- Execution Engine(실행 엔진)
class 파일을 실행시키는 역할
클래스 로더가 JVM내의 (런타임 데이터 영역에서) 바이트 코드를 배치시키면, 실행엔진이 이 바이트 코드를 실행함
자바 바이트코드는 기계가 바로 수행할 수 있는 언어보다는 비교적 인간이 보기 편한 형태로 기술된 것임
따라서 실행 엔진은 이와 같은 바이트코드를 실제로 JVM 내부에서 기계가 실행할 수 있는 형태로 변경하고 실행함
이 때 두 가지 방식을 사용하게 됨
- Interpreter(인터프리터)
실행 엔진은 자바 바이트 코드를 명령어 단위로 읽어서 실행함
명령어 단위로 한 줄 씩 수행하기 때문에 느림 (인터프리터 언어의 단점)
- JIT(Just - In - Time)
인터프리터 방식의 단점을 보완하기 위해 도입된 JIT 컴파일러
인터프리터 방식으로 실행하다가 적절한 시점에 바이트코드 전체를 컴파일하여 네이티브 코드로 변경하고,
이후에는 더 이상 인터프리팅 하지 않고 네이티브 코드로 직접 실행하는 방식
네이티브 코드는 캐시에 보관하기 때문에 한 번 컴파일된 코드는 빠르게 수행 가능함
JIT컴파일러가 네이티브 코드로 변경하는 컴파일 과정은 인터프리터보다 오래걸리므로
한 번만 실행되는 코드라면 JIT보다는 인터프리터를 사용하는 것이 유리함
따라서 JIT 컴파일러를 사용하는 JVM들은 내부적으로 해당 메서드가 얼마나 자주 수행되는지 체크하고,
일정 정도를 넘을 때에만 JIT컴파일을 수행함 (즉, 인터프리터를 보조해주는 역할)
- Garbage collector
Garbage Collector 를 수행하는 모듈 (쓰레드)이 있음
< Runtime Data Area 란? >
JAVA 프로그램을 수행하기 위해 OS에서 할당받은 메모리 공간
- PC Register
Program Counter Register
Thread 가 시작될 때 생성되며, 각 Thread 마다 하나씩 존재
Thread 가 어떤 부분을 어떤 명령으로 실행해야할 지 기록하는 부분
즉, PC Register 는 현재 수행 중인 JVM 명령의 주소를 저장하고 있음
- JVM 스택 영역
각 Thread 마다 하나씩 존재
프로그램 실행과정에서 메소드 내에 임시로 할당되었다가 메소드를 빠져나가면 바로 소멸되는 특성의 데이터를 저장하기 위한 영역
(메소드 내에서 선언된 지역 변수, 메소드의 매개 변수, 연산시 생성되는 임시 값,
메소드의 리턴값, 스레드, 메소드의 정보 등 메소드 내에서 임시적으로 사용되는 데이터들)
primitive type 변수가 여기 들어가며, (후술할 Heap 영역에 존재하는) Object 참조 변수 또한 여기 들어감
메소드 호출 시마다 각각의 스택 프레임(그 메서드만을 위한 공간)이 생성되고, 메서드 수행이 끝나면 프레임 별로 삭제를 함
- Native method stack
각 Thread 마다 하나씩 존재
JAVA가 아닌 다른 언어(Groovy, Kotlin, Scala 등)로 작성된 class 파일로부터 생성된 바이트 코드를 실행시키는 영역
- Method Area
모든 쓰레드가 공유하는 공유 자원
클래스 정보를 저장하기 위한 메모리 공간
다음과 같은 클래스 정보들이 저장됨
1) Field Information
멤버변수의 이름, 데이터 타입, 접근 제어자에 대한 정보
2) Method Information
메소드의 이름, 리턴타입, 매개변수, 접근제어자에 대한 정보
3) Type Information
class인지 interface인지의 여부, 저장 Type의 속성, 전체 이름, super class의 전체 이름(interface 이거나 object인 경우 제외)
4) Class Variable
Class 변수는 static 키워드로 선언된 변수를 의미
모든 인스턴스에 공유 되며 인스턴스가 없어도 직접 접근이 가능
변수는 인스턴스의 것이 아니라 클래스에 속하게 됨
클래스를 사용 하기 이전에 이 변수들은 미리 메모리를 할당 받은 상태가 됨
final class 변수는 상수로 치환 되어 Runtime Constant Pool에 값을 복사함
static 변수는 해당 영역에 저장되지만, 기본형이 아닌 static 클래스형 변수는 레퍼런스 변수만 저장되고
실제 인스턴스는 Heap에 저장되어 있음
Runtime Constant Pool이라는 별도의 관리 영역에는 상수 자료형을 저장하여 참조하고 중복을 막는 역할을 수행함
GC의 관리 대상에 포함됨
< Heap 영역 >
모든 쓰레드가 공유하는 공유 자원
Method Area 가 클래스 데이터를 위한 메모리 공간이라면 Heap 영역은 객체를 위한 메모리 공간
new 연산자를 통해 생성된 객체(Object)가 저장됨
객체는 Heap 영역에 있지만, 객체를 가리키는(참조하는) 변수는 JVM Stack 영역에 존재함
- Eden : 객체들이 최초로 생성되는 공간
- Survivor 0 / 1 : Eden에서 참조되는 객체 중 GC 로부터 살아남은 객체들이 저장되는 공간
- Old : Survivor 에서 참조되는 객체 중 GC로부터 살아남은 객체들이 저장되는 공간
- Permanent : Perm 영역은 보통 Class의 Meta 정보나 Method의 Meta 정보, Static 변수와 상수 정보들이 저장되는 공간으로 흔히 메타데이터 저장 영역이라고도 한다. 이 영역은 Java 8 부터는 Native 영역으로 이동하여 Metaspace 영역으로 변경되었다. (다만, 기존 Perm 영역에 존재하던 Static Object는 Heap 영역으로 옮겨져서 GC의 대상이 최대한 될 수 있도록 하였다)
최근 Java 8에서 JVM 메모리 구조적인 개선 사항으로 Perm 영역이 Metaspace 영역으로 전환되고 기존 Perm 영역은 사라지게 되었다. Metaspace 영역은 Heap이 아닌 Native 메모리 영역으로 취급하게 된다. (Heap 영역은 JVM에 의해 관리된 영역이며, Native 메모리는 OS 레벨에서 관리하는 영역으로 구분된다) Metaspace가 Native 메모리를 이용함으로서 개발자는 영역 확보의 상한을 크게 의식할 필요가 없어지게 되었다.(자주 OOM 이 나던 것을 방지)
참고 : https://johngrib.github.io/wiki/java8-why-permgen-removed/
< Generic 이란? >
컴파일 과정에서 타입체크를 해주는 제네릭은 자바에서 안정성을 맡고 있음
다양한 타입의 객체들을 다루는 메서드나 컬렉션 클래스에서 사용함
객체의 타입을 컴파일 시에 체크하기 때문에 객체의 타입 안전성을 높이고 형변환의 번거로움을 줄임
Collection 에 특정 객체만 추가될 수 있도록, 또는 특정한 클래스의 특징을 갖고 있는 경우에만 추가될 수 있도록 함
collection 내부에서 들어온 값이 내가 원하는 값인지 별도의 로직처리를 구현할 필요가 없어짐
또한 api 를 설계하는데 있어서 보다 명확한 의사전달이 가능해짐
제네릭 위치에 제네릭이 받을 수 있는 타입을 지정하면, 컴파일 때 해당 타입으로 캐스팅하여 사용
제네릭을 사용하면 잘못된 타입이 들어올 수 있는 것을 컴파일 단계에서 방지할 수 있음
클래스 외부에서 타입을 지정해주기 때문에 따로 타입을 체크하고 변환해줄 필요가 없어 편함
비슷한 기능을 지원하는 경우 코드의 재사용성이 높아짐
아래는 코드에서 제네릭을 사용하는 예
1
2
3
4
5
6
7
|
public class MyClass <T> { ... }
public class Main {
public static void main(String[] args) {
MyClass<Student> a = new MyClass<Student>();
}
}
|
cs |
반대로 Collection 에 Generic 외에 Raw 타입을 사용할 수 있는데
Raw 타입을 사용하면, 컴파일 타임이 아닌 Runtime 에 타입 체크를 하기 때문에 위험함
아래는 코드에서 Raw 타입을 사용하는 예
1
2
3
4
5
6
|
public void sameCode1 {
List list1 = Arrays.asList(1, 2, 3);
list1.add("4"); // 런타임 시에만 에러가 발견
String str = (String) list1.get(0);
System.out.println(str);
}
|
cs |
< final >
- final class : 다른 클래스에서 상속하지 못 함
- final method : 다른 메소드에서 오버라이딩하지 못 함
- final variable : 변하지 않는 상수값이 되어 새로 할당할 수 없는 변수가 됨
< Overriding vs Overloading >
- 오버라이딩(Overriding) : 상속해 준 부모 클래스 혹은 인터페이스에 존재하는 메소드를 하위 클래스에서 필요에 맞게 재정의하는 것
- 오버로딩(Overloading) : 이름은 같으나, 매개변수 타입이나 갯수가 다른 메소드를 만드는 것
< 접근제어자 >
접근제어자는 변수, 메소드의 접근 범위를 설정함
public : public 이 붙은 변수, 메소드는 어떤 클래스에서라도 접근이 가능
protected : protected 가 붙은 변수, 메소드는 동일 패키지 내의 클래스 또는 해당 클래스를 상속받은 다른 패키지의 클래스에서만 접근이 가능
default : 접근 제어자가 없는 변수, 메소드는 default 접근 제어자가 되어 해당 패키지 내에서만 접근이 가능
private : private 이 붙은 변수, 메소드는 해당 클래스에서만 접근이 가능
코드 예제 : https://wikidocs.net/232
< 얕은 복사 vs 깊은 복사 >
부등호 ( = ) 를 이용해서 객체를 복사하면, 참조값만이 복사됨
인스턴스의 내용까지 복사하려면 clone 메소드를 사용하거나, for문을 돌면서 직접 인스턴스의 값을 넣으면 됨
< 클래스 vs 객체 vs 인스턴스 >
클래스(Class) :
객체를 만들어 내기 위한 설계도 혹은 틀
연관되어 있는 변수와 메서드의 집합
객체(Object) :
소프트웨어 세계에 구현할 대상
클래스에 선언된 모양 그대로 생성된 실체
객체는 모든 인스턴스를 대표하는 포괄적인 의미를 갖음
oop 관점에서 클래스의 타입으로 선언되었을 때 ‘객체’라고 부름 (아래 예시 참고)
인스턴스(Instance) :
소프트웨어에 실체화(메모리에 할당됨)된 객체를 ‘인스턴스’라고 부름
인스턴스는 객체에 포함된다고 볼 수 있음
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
/* 클래스 */
public class Animal {
...
}
/* 객체와 인스턴스 */
public class Main {
public static void main(String[] args) {
Animal cat, dog; // '객체'
// 인스턴스화
cat = new Animal(); // cat은 Animal 클래스의 '인스턴스'(객체를 메모리에 할당)
dog = new Animal(); // dog은 Animal 클래스의 '인스턴스'(객체를 메모리에 할당)
}
}
//출처 : https://gmlwjd9405.github.io/2018/09/17/class-object-instance.html
|
cs |
< 쓰레드pool 과 fork-join pool >
쓰레드 풀이란, 쓰레드를 미리 만들어두고 그 안에서 쓰레드를 가져다가 사용할 수 있게 만든 것
쓰레드가 미리 만들어져있기 때문에 자원(메모리)이 이미 소모가 되어서
쓰레드 풀에 있는 쓰레드를 모두 사용하지 않으면 메모리가 낭비됨
또한, 쓰레드 풀에서 꺼내 쓴 쓰레드1, 쓰레드2 중에 쓰레드 1이 먼저 끝나면, 쓰레드 2가 끝날 때 까지
쓰레드1은 놀게되므로 낭비가 됨
위의 후자와 같이 꺼내온 쓰레드가 놀지 않도록 하는 것이 fork-join pool
fork-join pool은 각 쓰레드에게 각자 잡을 주지 않고, 하나의 잡을 나눠 갖도록 함
쓰레드 1, 쓰레드 2를 꺼내서 하나의 잡을 실행하도록 하였고
쓰레드 1이 잡을 잡고 작업을 진행
이 때 쓰레드 2는 쓰레드 1의 잡을 분할하여 자기가 가져와서 작업 진행
이렇게 놀게 되는 쓰레드가 있다면 일하고 있는 쓰레드로부터 작업을 분할해서 자기가 처리함으로써
노는 쓰레드가 없도록 함
잡 객체(?)가 분할하는 데 오버헤드가 있음(확실하지 않은 정보)
< 쓰레드 생애주기 >
NEW : 쓰레드가 처음 생성되었을 때 상태
RUNNABLE : 스케줄링 되기를 기다리는 쓰레드가 있는 큐
RUNNING : 실제 동작을 하는 쓰레드 상태
WAITING : system call 등에 의해 인터럽트되어 대기하는 쓰레드가 있는 큐
TERMINATE : 실행을 마치고 종료된 상태
< 쓰레드 스케줄링 >
과거에 사용된 JVM 내의 쓰레드는 JVM 이 관리하는 User Thread 였음
현대의 JVM 내의 쓰레드는 OS 에서 관리하는 Kernal Thread 임
JAVA 참고
https://github.com/JaeYeopHan/Interview_Question_for_Beginner/tree/master/Java
https://asfirstalways.tistory.com/158
https://d2.naver.com/helloworld/1329
https://st-lab.tistory.com/153
https://sgcomputer.tistory.com/64
https://gmlwjd9405.github.io/2018/09/17/class-object-instance.html
https://hamait.tistory.com/612
https://www.inflearn.com/questions/335824
'Coding Interview' 카테고리의 다른 글
[IT] CS 면접 대비 OS 질문 모음 (0) | 2022.01.07 |
---|---|
[IT] CS 면접 대비 Network 질문 모음 (0) | 2021.12.30 |
[IT] CS 면접 대비 Database 질문 모음 (0) | 2021.12.29 |
코딩 검사할 때 엣지케이스 찾기 리스트 (0) | 2020.06.14 |
[2020 KAKAO BLIND RECRUITMENT] 자물쇠와 열쇠 (0) | 2020.06.10 |