지나공 : 지식을 나누는 공간
[직무인터뷰] JAVA 이야기 - 1편 본문
자주 나오는 질문이라고 들은 건 형광펜 표시했습니다.
대답에 꼭 들어가야 하는 키워드는 컬러펜 표시했습니다.
1번 질문에 대한 꼬리질문은 1-1, 1-2와 같이 정리합니다. 2편도 있어요!!!!!!
설명이 기니까 이 포스팅에서의 질문 목차를 먼저 작성하겠다.
- 절차지향과 객체지향이 뭔가?
- Java의 특징이 뭔가?
- 객체지향 언어의 특징이 뭔가?
- 오버라이딩과 오버로딩이 뭔가?
- 운영체제에 독립적인 이유는 뭔가?
- JVM의 동작방식과 구조를 설명해봐라
- 가비지 컬렉터에 대해 설명해봐라
- 그 동작과정은?
- 객체지향 언어의 특징이 뭔가?
1. 절차지향과 객체지향이 뭔가?
절차지향은 순차적인 처리를 중시한다.
장점은 컴퓨터 처리 구조와 유사해서 실행 속도가 빠름.
단점은 유지보수와 디버깅이 어렵고 실행 순서가 정해져 있어서 순서가 바뀌었을 때 동일한 결과를 보장하기 어렵다는 점.
객체지향은 데이터와 절차를 하나의 덩어리로 묶어서 각각을 모델링하여 개발하는 방법이다.
장점은 코드의 재활용성이 높고 디버깅이 쉽다는 점.
단점은 처리 속도가 느리다는 점.
2. Java의 특징이 뭔가?
[특징 구조]
- 객체지향적 언어
- 추상화
- 캡슐화
- 상속
- 다형성
- 운영체제에 독립적이다.
- JVM(Java Virtual Machine)에서 동작하기 때문에 운영체제에 독립적
- 자동으로 메모리 관리
- GC가 자동으로 메모리 관리
- 그 외 ) 네트워크와 분산처리 지원, 멀티쓰레드 지원, 동적 로딩 지원
[설명]
객체지향 언어의 특징에는 추상화, 캡슐화, 상속, 다형성이 있다.
추상화는 객체들의 공통된 속성이나 기능을 묶어서 이름을 붙이는 것이다.
예를 들어 자바에서는 객체를 추상화해서 클래스를 만든다고 표현한다. 실제 객체가 존재하고, 그러한 객체들의 공통된 속성과 기능만 뽑아 가지고 있는 클래스가 있다. 또 구조체의 선언처럼 어떠한 정보를 묶음으로 표현할 때 우리가 필요한 정보들의 형태만 모아서 선언해주는 것도 추상화다.
캡슐화는 내부의 동작 방법을 숨기고 사용자에게는 사용 방법만을 노출시키는 것이다.
중요 정보를 숨겨서 잘못된 수정을 막고(정보은닉), 사용자가 쉽게 사용할 수 있도록 만든다.
예를 들어, 함수를 사용할 때 사용하는 외부에서는 그 함수에 어떤 값을 주면 어떤 값을 결과로 받아오는 것만 알지 그 함수 내부가 어떤 코드로 동작하는 지는 모른다. private 변수에 접근할 때 getter나 setter를 통해 간접적으로 접근하는 것도 캡슐화와 연관된다.
상속은 코드의 재사용성을 극대화하기 위한 장치로, 부모 클래스의 속성을 그대로 물려받는 것을 말한다.
물론 설정에 따라 완전히 같지는 않을 수도 있다.
다형성은 하나의 클래스나 메소드가 다양한 방식으로 동작이 가능한 것을 의미하는데 오버라이딩과 오버로딩이 있다.
2-1 : 오버라이딩과 오버로딩이 뭔가?
오버로딩은 같은 이름의 메소드를 여러 개 정의하는 것으로 매개변수의 타입이 다르거나 매개변수의 개수가 달라야 한다. return type이나 접근 제어자는 영향을 주지 않는다.
오버라이딩은 상속에서 나온 개념으로 부모 클래스의 메소드를 자식 클래스에서 재정의하는 것이다.
또한, 자바는 JVM에서 동작하기 때문에 운영체제에 독립적이다.
자바가 나오기 전의 프로그래밍 언어들은 window, linux와 같은 운영체제가 이해할 수 있도록 이런 운영체제에 맞는 변환 과정을 거쳐야 했다. 운영체제에 종속적이었던 것이다. 하지만 자바는 운영체제가 아닌 JVM과만 통신한다. 응용프로그램으로부터 전달받은 내용을 JVM이 알아서 해당 운영체제가 이해할 수 있도록 변환해서 전달해준다. 그래서 자바와 운영체제는 서로 독립적이고, JVM과 운영체제는 서로 종속적인 관계다. 다시 정리하면, 자바는 운영체제를 고려하지 않고 코드를 작성하면 JVM이 이걸 각 운영체제에 맞게 변환시켜주니까 개발자가 더욱 코딩에만 집중할 수 있다.
2-2 : JVM 의 동작 방식과 구조가 어떻게 되어 있나?
아래에서부터는 JVM의 런타임 데이터 영역을 세분화해서 공부할 것이다. 그러니 그 전에 전반적인 메모리 영역을 살피자.
JVM은 컴파일된 바이트코드를 실행시켜주는 가상의 컴퓨터다.
JVM에 컴파일된 바이트 코드가 오면 JVM이 이를 각 운영체제에 맞게 기계어로 변환하고 실행시킨다.
1) 작성한 자바 코드가 우리가 javac로 접했던 자바 컴파일러를 통해 .class 확장자를 가진 바이트코드로 컴파일된다.
2) 컴파일된 바이트 코드는 JVM의 클래스로더에 전달된다.
3) 클래스로더는 동적로딩을 통해 필요한 클래스들을 로딩 및 링크해서 런타임 데이터 영역, 즉 JVM의 메모리에 올린다.
4) 실행엔진은 JVM의 메모리에 올라온 바이트코드를 명령어 단위로 하나씩 가져와서 실행한다.
jvm과 관한 자세한 내용을 참고할 블로그인데, 일반적인 면접에서 아래 블로그처럼 자세한 내용을 묻지는 않는 거 같아서 이 포스팅에 추가하진 않지만 꼭 읽어보자!!! : https://d2.naver.com/helloworld/1230
JVM의 핵심 : 메모리 영역을 가지고 있다. & 코드 영역에 올라온 바이트코드를 기계어로 해석해서 실행시켜 준다.
2-3. 자바의 메모리 구조에 대하여 설명할 수 있는가?(GC 관련)
그럼 이제 메모리(런타임 데이터 영역)의 내부를 살펴보자.
main 메소드와 static메소드의 코드는 해당 클래스 파일이 최초 호출될 때 메모리에 올라간 후 프로그램 종료 시까지 계속 남아있다. 그 외의 코드는 필요할 때만 메인 메모리에 올라왔다가 다 쓰고 나면 가비지 컬렉터(GC)가 없애 버린다.
[자세한 과정을 살펴보자. -> 넘기고 싶으면 넘겨도 될 내용. 이해하고 싶다면 읽어야 할 내용]
이런 이유로 static 메소드는 별도 객체 생성 없이도 언제 어디서나 접근해 사용할 수 있는 것이다. 반면 일반 메소드 코드는 평소에는 메모리에 없다가 객체를 생성해서 해당 클래스의 바이트 코드가 JVM의 클래스 로더에 의해 로딩 및 링킹되어 메모리에 올라가야 그 메소드를 사용할 수 있다.
아래 프로그램을 실행한다고 하자.
package study.first;
/* Public class */
public class Main {
static int a = 3;
int b = 5;
static String str = "abc";
/* static 메소드 */
public static void main(String[] args) {
Example e1 = new Example();
Main m1 = new Main();
e1.sum();
m1.print();
}
/* 일반 메소드 */
public void print() {
int val = 10;
String val2 = "abc";
System.out.println("print 메소드 실행");
}
}
/* Default Class */
class Example {
char c = 'a';
String arr = "abc";
void sum() {
String val3 = "abc";
System.out.println("sum 메소드 실행");
}
}
}
처음 프로그램을 실행하면 아래 그림처럼 세팅된다. Main.class 파일에 있는 static 메소드와 static 필드를 찾아서 전부 메모리에 올려준다. s라고 되어있는 게 모두 static 값이다. main 메소드의 코드는 코드 영역 중에서도 코드를 저장하는 공간에 들어간다.
코드 영역 내 상수영역은 static 필드나 코드 안에 포함된 변수를 저장하는 영역이다. 그리고 코드/힙/스택 의 영역들은 모두 기본형 변수(int, char, long 등)는 들어온 그 자리에 저장하고 그 외 참조변수(class, interface, array 등)는 레퍼런스, 포인터 변수만 저장하면서 예네가 가리키는 실제 데이터를 힙 영역에 저장한다. c언어에서는 개발자가 직접 생성하지 않는 한 힙 영역을 사용하지 않지만, Java는 모든 참조 변수의 실제 데이터들을 힙 영역에 저장한다. 아래에 나오겠지만 해당 코드에서 e1은 객체니까 참조변수이고 그렇기 때문에 스택에는 객체의 주소가 저장되고 힙 영역에는 실제 객체 데이터가 저장된다.
그 후 main 메소드가 실행된다. 아래의 그림은 main 메소드가 실행됐을 때 e1의 객체를 생성한 뒤의 메모리 모습이다. 메소드를 실행하니까 새로운 스택 프레임이 생성되면서 main에서 만든 다른 클래스의 메소드를 실행하려고 한다. static메소드가 아닌 메소드들은 코드가 메모리에 없으니까 먼저 이들을 메모리에 올려주는 작업이 필요한데, 이 작업이 바로 new를 사용해서 인스턴스(객체)를 생성하는 코드다.
Example e1 = new Example(); 을 통해 인스턴스가 하나 만들어지면, 힙 영역에 해당 클래스의 설계에 맞게 공간이 할당되고 초기값이 입력된다. 실제 데이터는 힙에 있고 이 객체의 주소를 갖는 e1변수가 스택 영역에 있게 된다. 객체 내에서도 기본형 변수는 힙에 있는 실제 객체 데이터 자체가 갖지만 그 내부에 있는 참조 변수인 String은 주소만 가지고 있고 실제 데이터는 다른 곳에 있어서 그 지점을 가리키게 된다. 현재 "abc"는 이미 힙 영역 내 다른 곳에 저장되어 있으니 기존의 이 문자열을 가리키게 된다.
이번엔 main에서 e1을 생성한 뒤의 코드인 Main m1 = new Main();을 실행할 때의 메모리 모습이다. e1과 동일하게 동작하지만 Main 이라는 클래스에서 static 필드와 메소드였던 int a 와 String str은 이미 메모리에 올라가 있다는 점이 다르다. 따라서 m1이라는 변수는 힙에 만들어지는 실제 객체 데이터의 주소를 갖은 채로 스택에 올라가고, 실제 인스턴스의 데이터는 힙에 올라가며, 그 인스턴스 중 static변수였던 a랑 str은 기본형이더라도 실체를 갖는 것이 아니라 기존에 상수영역에 들어가있는 객체를 가리키는 주소값을 가진다.
그 후 e1.sum()을 실행하면 아래 그림처럼 하나의 스택 프레임이 더 생기면서 sum 메소드 안에 있는 변수를 저장한다. 그 안에는 String val3만 있고 이는 참조변수이므로 스택 프레임에서는 val3의 실데이터의 주소를 가지고 있고, 실제 val3 데이터인 "abc"를 힙에 저장해야 하는데, 이는 또 기존의 데이터가 있기 때문에 기존의 힙에 있는 "abc"를 가리키게 된다.
여기서 System.out.println("sum 메소드 실행")을 하면 해당 값이 출력되면서 함수가 종료되고, 그와 동시에 sum과 관련해서 생겼던 스택 프레임이 제거된다.
이후 m1.print()함수도 e1.sum()과 동일한 방식으로 실행되고, main메소드가 끝나면 프로그램이 종료된다. 앞서 말했듯이 예시의 main 메소드와 같은 static 메소드들은 클래스 파일이 최초 호출될 때 메모리에 올라갔다가 프로그램이 종료될 때까지 남아있는다. 그래서 main이 끝나면 해당 스택프레임이 사라진다. 그리고 static이 아닌 코드들은 필요할 때만 메모리에 올라가고 다 쓰면 가비지 컬렉터가 없앤다.
여기서 가비지컬렉터가 없애는 동작을 살펴보기 위해, 현재는 main메소드로 되어 있는 것을 static이 아닌 다른 메소드라고 가정을 해서 GC가 어떻게 메모리에서 코드들을 없애는지 살펴보자. (main, static은 GC가 없애주는게 아니라 프로그램 종료 시에 사라지니까)
메소드가 종료되면 해당 스택 프레임이 사라진다. 그러면서 그 내부에 있던 e1, m1변수도 사라지니까 이제 힙 영역에 할당되어 있는 e1, m1의 인스턴스를 참조하는 곳이 없어졌다. 더 이상 참조될 일이 없으니 힙 영역 속 이 데이터들은 쓰레기가 된다. 인스턴스에 들어있는 메소드 코드들도 모두 쓸 일이 없어졌으니 이것들까지 모두 묶어서 GC(가비지 컬렉터)가 이들을 처리한다. c언어에서는 malloc한 메모리를 직접 free 해주는데 java는 GC가 이 역할을 자동으로 한다.
단, GC는 static이 붙은 요소는 건드리지 않는다. static이 붙어있던 int a, String str은 남아 있다.
2-4. GC(Garbage Collector)가 어떻게 동작하는가?
java의 가비지 컬렉터는 기본적으로 Mark-and-Sweep 알고리즘을 따른다.
1단계 : Marking
가비지 컬렉터는 메모리에서 live object를 확인해서 unreacheable object (접근불가능객체)가 뭔지 마킹한다.
2단계 : Normal Deletion(삭제)
가비지 컬렉터가 unreacheable object를 삭제한다.
3단계 : Deletion with Compacting(압축과 삭제를 동시에)
가비지 컬렉터 중 일부는 memory를 효과적으로 쓰기 위해 접근 불가능 객체들을 삭제하면서 동시에 압축을 진행하기도 한다.
* Java Garbage Collector의 종류
1. Serial GC : 주로 CPU 코어가 1개 있을 때 동작하는 방식으로 GC를 처리하는 쓰레드가 1개다.
2. Parallel GC : GC를 처리하는 쓰레드가 여러 개여서 Serial GC보다 객체를 빠르게 처리한다. 메모리가 충분하고 CPU 코어 개수가 많을 때 사용하는 것이 좋다.
*GC 관련 용어
Young Generation :
최초로 새 객체가 생성됐을 때 heap 영역에서 위치하는 공간으로, MinorGC가 발생하면 접근불가능 객체는 삭제되고 참조되고 있는 객체(surviving object)는 Old Generation으로 이동한다.
Old Generation :
Long Surviving Object(오랜 기간 참조되고 있는 객체)들이 머무는 공간으로, 이 공간이 가득 차면 MajorGC가 발생한다.
Minor GC :
Young 영역에 객체가 생성되었다가 사라지는 것을 말한다. Young 영역에서 GC가 발생하는 것.
MajorGC :
Old 영역에 객체가 생성되었다가 사라지는 것을 말한다. Old 영역에서 GC가 발생하는 것.
Full GC :
힙 영역 전체를 claer하는 GC 작업
Stop the world :
Minor GC 발생 시에 일어나는 이벤트로, Minor GC를 수행할 때는 Minor GC의 쓰레드 외에 모든 어플리케이션의 쓰레드가 중단되는 것. 예외 없다. (GC를 실행하기 위해 JVM이 GC 외의 어플리케이션의 실행을 멈추는 것이다.)
GC 관련 내용은 이어서 다른 포스팅으로 적을 예정이다.
일단 다른 질문들을 보러 JAVA 2편으로 ..!
https://codevang.tistory.com/83?category=827598 와 https://d2.naver.com/helloworld/1329 와 https://myeongmy.tistory.com/61 에서 참고해서 정리했다.
'Tech Interview' 카테고리의 다른 글
[직무인터뷰] JPA 이야기 (1) | 2021.06.07 |
---|---|
[직무인터뷰] Spring과 DB 이야기 - 2편 (0) | 2021.06.06 |
[직무인터뷰] Spring과 DB 이야기 - 1편 (2) | 2021.06.05 |
[직무인터뷰] 네트워크 이야기 (0) | 2021.05.21 |
[직무인터뷰] JAVA 이야기 - 2편 (0) | 2021.05.19 |