기술 면접 준비

[기술면접] JAVA - 4/4

Lea Hwang 2023. 3. 3. 16:51

[기술면접] JAVA - 4/4의 목차

  • 제네릭에 대해 설명해주시고, 왜 쓰는지 알려주세요.
  • final / finally / finalize 의 차이를 설명해주세요.
  • 직렬화(Serialize)
  • 가비지 컬렉션(G1GC)
  • 메모리, 성능을 개선하기 위해 생각나는 방법은?
  • 동일성과 동등성의 차이는 무엇인가?
  • 어노테이션이란?
  • 스트림이란?

 

 


 

 

 

😎 제네릭에 대해 설명해주시고, 왜 쓰는지 알려주세요.

  • 제네릭이란?
    • Data type을 특정한 type 하나로 정하지 않고 사용할 때마다 바뀔 수 있게 범용적이고 포괄적으로 지정한다 라는 의미입니다.
    • Java 5부터 제네릭타입이 추가되었고 제네릭 타입은 < >을 가지는 클래스와 인터페이스를 말합니다. 
    • < > 안에는 참조자료형(클래스, 인터페이스, 배열)만 가능합니다. 기본자료형을 이용하기 위해선 wrapper 클래스를 활용해야합니다.

▶ 꼬리질문 - 제네릭에대해 자세히 말씀해주세요.

  • 제네릭을 사용해야하는 이유
  • 제네릭 장점
  • 제네릭 특징
  • 제네릭 사용법
  • 제네릭 주요 개념(바운디드 타입, 와일드 카드)
  • 제네릭 메소드 만들기

 

제네릭을 사용해야하는 이유

  • 제네릭 타입을 사용함으로써 잘못된 타입이 사용될 수 있는 문제를 컴파일 과정에서 제거할 수 있다.
  • 실행 시 타입 에러가 나는것보다는 컴파일 시에 에러를 사전에 방지하는 것이 좋다.
  • 또 제네릭 코드를 사용하면 요소를 찾아올 때 타입변환을 할 필요가 없어 프로그램 성능이 향상된다.

 

 

제니릭 장점

1. 타입 안정성 제공

-  컴파일 타임에 타입 체크를 하기 때문에 런타임에서 ClassCastException과 같은 UncheckedException을 보장받음

 

2. 타입체크와 형변환 생략 가능

-  코드가 간결해짐

 

 

 

제니릭 특징

모든 객체에 대해 동일하게 동작해야하는 static멤버에 타입 변수 T를 사용할 수 없다.

-  T는 인스턴스변수로 간주되기 때문이다.

-  static 멤버는 인스턴스변수를 참조할 수 없다.

 

제네릭 타입의 배열을 생성하는 것도 허용되지 않는다.

-  제네릭 배열 타입의 참조변수를 선언하는 것은 가능하지만, new T[10] 과 같이 배열을 생성하는 것은 안된다.

이유는 new 연산자 때문이다.  이 연산자는 컴파일 시점에 타입 T가 무엇인지 명확히 알아야 하기 때문이다.

* 꼭 제네릭 배열을 생성해야 할 필요가 있을 땐 new 연산자 대신  'Reflection API'의 new Instance()와 같이 동적으로 객체를 생성하는 메소드로 생성하거나, Object 배열을 생성해서 형변환 하는 방법 등 사용 

 

 

 

제네릭 사용법

public class 클래스명<T> {...}
public interface 인터페이스명<T> {...}

제네릭 타입은 타입을 파라미터로 가지는 클래스와 인터페이스를 말한다. 

제네릭 타입은 클래스 또는 인터페이스 이름 뒤에 < > 부호가 붙고 사이에 타입 파라미터가 위치한다.

타입 파라미터는 정해진 규칙은 없지만 일반적으로 대문자 알파벳 한글자로 표현한다.

 

 

자주 사용하는 타입인자

 

제네릭 클래스

class ExGeneric<T> {
	private T t;
    
    public void setT(T t) { 
    	this.t = t;
    }
    
    public T getT() {
    	return t;
    }
}

위와 같이 클래스를 설계할 때 구체적인 타입을 명시하지 않고 타입 파라미터로 넣어두었다가

실제 설계한 클래스가 사용될 때  ExGeneric<String> exGeneric = new ExGeneric< >( );   이런식으로 

구체적인 타입을 지정하면서 사용하면 타입 변환을 최소화 시킬 수 있다.

 

 

제네릭 인터페이스

interface ExInterfaceGeneric<T> { 
	T example();
}
 
class ExGeneric implements ExInterfaceGeneric<String> {
	
    @Override
    public String example() { 
    	return null;
    }
}

인터페이스도 위와 같이 클래스처럼 제네릭으로 설정해두고 활용할 수 있다.

 

 

 

제네릭 주요 개념

한정적 타입 매개변수(Bounded Type)

타입 파라미터들은 바운드(bound) 될 수 있다.

바운드 된다는 의미는 제한된다는 의미인데 메소드가 받을 수 있는 타입을 제한할 수 있다는 것이다.

 

예를들면, 어떤 타입과 그 타입의 모든 서브 클래스들을 허용하거나,

어떤 타입과 그 타입의 모든 부모클래스들을 허용하도록 메소드를 작성할 수 있다.

위와 같이 컴파일 에러가 난다.

GenericTest 클래스의 Type 파라미터를 T로 선언하고 <T extends Number >로 선언한다.

GenericTest의 타입으로 Number의 서브 타입만 허용한다는 것이다.

 

Integer는 Number의 서브타입인데  set메소드의 인자로 문자열(String)을 전달하려고 했기 때문에 컴파일 에러가 발생하게된다.

 

 

 

제네릭 와일드 카드

코드에서 ?를 일반적으로 와일드카드라고 부른다. 사용하는 경우는 아래와 같다.

와일드카드 타입에는 총 세가지의 형태가 있으며 물음표(?) 키워드로 표현된다.

 

<?> :  타입 파라미터를 대치하는 것으로 모든 클래스나 인터페이스타입이 올 수 있다.

-  A  ~  E 모두 가능

 

<? extends 상위타입> :  객체의 하위 클래스만 올 수 있다.

- <? extends D>  :  D , E 가능

 

<? super  하위타입> :  객체의 상위 클래스만 올 수 있다.

- <? super D>  :  D,  A 가능

 

 

 

제네릭 메소드

제네릭 메소드는 매개 타입과 리턴 타입으로 타입 파라미터를 갖는 메소드를 말한다.

구현을 하기 위해선 리턴 타입 앞에 < >다이아몬드 기호를 추가하고, 타입 파라미터를 기술한 다음 리턴 타입과 매개 타입으로 타입 파라미터를 사용하면 된다.

public <타입 파라미터 . . . > 리턴타입 메소드명(매개변수, . . . ) {  . . .  }

public <T>  Box<T>  boxing(T t) {  . . .  }

 

제네릭 메소드는 두 가지 방식으로 호출할 수 있다.

1. 리턴타입 변수 = <구체적 타입> 메소드명(매개값);     //명시적으로 구체적 타입 지정
Box<Integer> box = <Integer>boxing(100);                    //타입 파라미터를 명시적으로 Integer로 지정

2. 리턴타입 변수 = 메소드명(매개값);                            //매개값을 보고 구체적 타입을 추정
Box<Integer> box = boxing(100);                                  //타입 파라미터를 Integer로 추정

일반적으로 매개값을 넣어줌으로 컴파일러가 유추하게 만들어주는 두번째 방법을 사용한다.

 

 

 

 

😎 final / finally / finalize 의 차이를 설명해주세요.

  • final은 클래스, 메소드, 변수, 인자를 선언할 때 사용할 수 있으며, 한 번만 할당하고 싶을 때 사용합니다.
    • final 변수는 한 번 초기화되면 그 이후에 변경할 수 없습니다.
    • final 메소드는 다른 클래스가 이 클래스를 상속할 때 메소드 오버라이딩을 금지합니다.
    • final 클래스는 다른 클래스에서 이 클래스를 상속할 수 없습니다.
  • finally는 try-catch와 함께 사용되며, try-catch가 종료될 때 finally block이 항상 수행되기 때문에 마무리 해줘야 하는 작업이 존재하는 경우에 해당하는 코드를 작성해주는 코드 블록입니다.
  • finalize는 Object 클래스에 정의되어 있는 메소드이며, GC에 의해 호출되는 메소드로 절대 호출해서는 안되는 메소드입니다. GC가 발생하는 시점이 불분명하기 때문에 해당 메소드가 실행된다는 보장이 없고,
    finalize() 메소드가 오버라이딩 되어 있으면 GC가 이루어질 때 바로 Garbage Collectiong 되지 않아 지연되면서 OOME(Out of Memory Exception)이 발생할 수 있기 때문에 finalize() 메소드를 오버라이딩하여 구현하는 것을 권장하지 않고 있습니다.

 

 

 

😎 직렬화(Serialize)에 대해 설명해주세요.

  • 메모리를 디스크에 저장하거나, 네트워크 통신에 사용하기 위한 형식으로 변환하는 것이다. 
  • 특별히, 자바 직렬화는 자바 시스템 내부에서 사용되는 객체 또는 데이터를 외부의 자바 시스템에서도 사용할 수 있도록 바이트 형태로 데이터 변환하는 기술을 뜻한다.
  • 반대로 직렬화된 바이트 형태의 데이터를 다시 객체로 변환하는 과정을 '역직렬화'라고 합니다.
  • (간단히) JVM의 메모리에 상주(힙 or 스택)되어 있는 객체 데이터를 바이트 형태로 변환하는 기술

 

▶꼬리질문 - 직렬화가 왜 필요한가?

디스크에 저장하거나 통신할 때는 값 형식 데이터만 사용할 수 있다. 참조 형식 데이터는 실제 데이터 값이 아닌 힙에 할당되어 있는 메모리 번지 주소를 가지고 있기 때문이다.

이때 직렬화를 하게 되면 각 주소 값이 가지는 데이터를 전부 끌어 모아서 값 형식 데이터로 변환해 준다. 

직렬화가 된 데이터는 언어에 따라서 텍스트 또는 바이너리 등의 형태가 되는데, 이러한 형태가 되었을 때 저장하거나 통신할 때 파싱이 가능한 유의미한 데이터가 된다.

 

 

▶꼬리질문 - 역직렬화란?

디스크에 저장한 데이터를 읽거나, 네트워크 통신으로 받은 데이터를 메모리에 쓸 수 있도록 변환하는 것이다. 

특별히, 자바 역직렬화는 바이트로 변환된 데이터를 다시 객체로 변환하는 기술을 뜻한다.

 

 

▶꼬리질문 - SerialVersionUID를 선언해야 하는 이유에 대해 설명해주세요.

  • JVM은 직렬화와 역직렬화를 하는 시점의 클래스에 대한 버전 번호를 부여하는데, 만약 그 시점에 클래스의 정의가 바뀌어 있다면 새로운 버전 번호를 할당하게 됩니다. 그래서 직렬화할 때의 버전 번호와 역직렬화를 할 때의 버전 번호가 다르면 역직렬화가 불가능하게 될 수 있기 때문에 이런 문제를 해결하기 위해 SerialVersionUID를 사용합니다.
  • 만약 직렬화할 때 사용한 SerialVersionUID의 값과 역직렬화 하기 위해 사용했던 SVUID가 다르다면 InvalidClassException이 발생할 수 있습니다.

 

 

 

 


[ Garbage Collection ]

😎 Garbage Collection이란?

질문의도

JVM 의 구조, 특히 Heap Area 에 대한 이해

답변

  • 자바가 실행되는 JVM 에서 사용되는 객체, 즉 Heap 영역의 객체를 관리해 주는 기능을 말합니다.
  • 동적으로 할당한 메모리 영역 중 사용하지 않는 영역을 탐지하여 해제하는 기능입니다.
  • 이 과정에서 stop the world 가 일어나게 되며, 이 일련 과정을 효율적으로 하기 위해서는 가비지 컬렉터 변경 또는 세부 값 조정이 필요합니다.

▶ 꼬리질문 -  GC의 필요성에 대해 말씀해주세요.

Java Runtime시 Heap 영역에 저장되는 객체들은 따로 정리하지 않으면 계속해서 메모리에 쌓이게되어 OutOfMemmory Exception이 발생합니다.

WAS 같은 경우 WAS가 다운될 수가 있는데 이를 방지하기 위하여 JVM에서는 주기적으로 사용하지 않는 객체를 수집하여 정리해주어야합니다. 이 역할을 하는게 GC입니다.

 

▶ 꼬리질문 - GC의 장단점은 어떤 게 있을까요?

장점

  • 메모리를 수동으로 관리하던 것에서 비롯된 에러를 예방할 수 있다.
    • 개발자의 실수로 인한 메모리 누수
    • 해제된 메모리를 또 해제하는 이중 해제
    • 해제된 메모리에 접근

단점

  • GC의 메모리 해제 타이밍을 개발자가 정확히 알기 어렵다.
  • 어떠한 메모리 영역이 해제의 대상이 될 지 검사하고, 실제로 해제하는 일이 모두 오버헤드다.

 

 

 

 

😎 자바에서 사용하는 GC알고리즘은 무엇인가요?

Mark and Sweep 동작 알고리즘입니다.

  • Root Space부터 해당 객체에 접근 가능한지, 아닌지를 메모리 해제의 기준으로 삼는다.
  • Root Space부터 그래프 순회를 통해 연결된 객체를 찾아내고(Mark) 연결이 끊어진 객체는 지운다.

 

▶ 꼬리질문 - GC의 수거대상은 어떤건가요?

GC Roots로부터 참조를 탐색했을 때 연결이 끊어진 객체입니다.

 

 

▶ 꼬리질문 - GC 알고리즘의 목적은 무엇인가요?

JVM의 GC 알고리즘의 중요 목적은 크게 두가지로 나눌 수 있다.

1) 어떻게 불필요한 object들을 선별 하는 가
2) GC가 동작하는 동안 프로그램이 중단 되는 시간을 어떻게 줄 일 수 있는가

이 목적을 달성하기 위해 GC는 불필요한 Object를 발견하고 해당 메모리 주소를 되 찾는(Reclaim) 방법으로 Mark and Sweep 알고리즘을 사용한다.

 

 

▶ 꼬리질문 - 어떤 순서로 GC가 동작하나요?

1) Marking - GC Root로 부터 모든 변수를 스캔하면서 각각 어떤 객체를 참조하고 있는지 찾아서 마킹한다.

2) Sweep - Unreachable한 객체들을 Heap에서 제거한다.

3) Compact (optional) - Sweep 후에 분산된 객체들을 Heap의 시작 주소로 모아 메모리가 할당된 부분과 그렇지 않은 부분으로 나눈다.

 

 

 

😎 Stop-the-world란 무엇인가요?

  • 가비지 컬렉터를 실행하기 위해 JVM이 애플리케이션 실행을 멈추는 것
  • Stop-the-world가 발생하면 가비지 컬렉터를 실행하는 쓰레드를 제외한 나머지 쓰레드는 모두 작업을 멈춘다.
  • 가비지 컬렉터 작업을 완료한 이후에 중단했던 애플리케이션 실행을 다시 시작한다.
  • 어떤 가비지 컬렉터 알고리즘을 사용하더라도 stop-the-world는 발생한다.
  • 대개의 경우 가비지 컬렉터 튜닝이란 stop-the-world 시간을 줄이는 것이다.

 

 

😎 G1GC (Garbage First Garbage Collector)

  • 쓰레기로 가득찬 heap 영역을 집중적으로 수집한다.
  • 큰 메모리를 가진 멀티 프로세서 시스템에서 사용하기 위해 개발된 가비지 컬렉터이다.
  • G1GC의 목표는 가비지 컬렉터의 일시 정지시간을 최소화 하면서, 따로 설정을 하지 않아도 가능한 한 처리량을 확보하는 것이다.
  • G1은 실시간 가비지 컬렉터가 아니다. 일시 정지시간을 최소화하되 완전히 없애지는 못한다.

 

 

▶ 꼬리질문 - G1GC의 작동방식에 대해 설명해주세요.

G1GC는 전체 heap을 체스판처럼 여러 영역(region)으로 나누어 관리합니다.

  • 비어 있는 영역에만 새로운 객체가 들어간다.
  • 쓰레기가 쌓여 꽉 찬 영역을 우선적으로 청소한다.
  • 꽉 찬 영역에서 라이브 객체를 다른 영역으로 옮기고, 꽉 찬 영역은 깨끗하게 비운다.

 

 

▶ 꼬리질문 - G1GC의 장단점은 어떤 게 있을까요?

 

G1은 Garbage First의 약어로 Garbage만 있는 Region을 먼저 회수한다고 해서 붙여진 이름이다. 

 

  • 장점
    • Heap 크기가 클수록 잘 동작한다.
    • 처리 속도가 빠르다.
    • Garbage로 가득찬 영역을 빠르게 회수하여 빈 공간을 확보하므로 GC 빈도가 줄어든다.
  • 단점
    • 공간 부족 상태를 조심해야 한다. (Minor GC, Major GC 수행하고 나서도 여유 공간이 부족한 경우)
      • 이때는 Full GC가 발생하는데, 이 GC는 Single Thread로 동작한다.
      • Full GC는 heap 전반적으로 GC가 발생하는 것을 뜻한다.
    • 작은 Heap 공간을 가지는 Application에서는 제 성능을 발휘하지 못하고 Full GC가 발생한다.

 


 

 

 

😎 메모리, 성능을 개선하기 위해 생각나는 방법은?

static을 사용해 선언한다.

인스턴스 변수에 접근할 일이 없으면, static 메소드를 선언하여 호출하자

모든 객체가 서로 공유할 수 있기 때문에 메모리가 절약되고 연속적으로 그 값의 흐름을 이어갈 수 있는 장점이 존재한다.

 

 

 

😎 동일성과 동등성의 차이는 무엇인가?

 

두 객체가 할당된 메모리 주소가 같으면 동일하고, 두 객체의 내용이 같으면 동등하다고 말한다.

동일성은 `==` 연산자를 통해 판별할 수 있고,

동등성은 `equals` 연산자를 통해 판별할 수 있다.

▶ 꼬리질문 - == 연산자와 equals연산자의 차이를 말씀해주세요.

`==` 연산자는 객체의 동일성을 판별하기 위해 사용하며, `equals` 연산자는 두 객체의 동등성을 판별하기 위해 사용한다.

 

 

`equals` 연산자는 재정의하지 않으면 내부적으로 `==` 연산자와 같은 로직을 수행하므로 차이가 없다.

따라서 `equals` 연산자는 각 객체의 특성에 맞게 재정의를 해야 동등성의 기능을 수행한다.

 

 

 

😎 어노테이션이란?

 

어노테이션은 작성한 코드에 대해 추가적인 정보를 제공하면서 컴파일 타임 혹은 런타임 시점에서 해당 코드에 필요한 추가적인 처리를 해줍니다.

표준 어노테이션의 종류 중 @Override는 컴파일러에게 오버라이딩하는 메소드라는 것을 알려줍니다.

 

 

 

 

😎 스트림이란?

 

스트림(Stream)은 자바 8부터 추가된 컬렉션의 저장 요소를 하나씩 참조해서 람다식으로 처리할 수 있는 반복자입니다.

 

 

▶ 꼬리질문 - 스트림의 특징은?

▶ 꼬리질문 - 스트림 사용시 주의해야할 점은?

 

 

스트림의 특징은?

 

  • 스트림이 제공하는 대부분의 메소드는 함수형 인터페이스를 매개 타입으로 가지기 때문에 람다식으로 요소 처리 코드를 제공한다.
  • 스트림은 원본 데이터를 조회하여 별도의 요소들로 스트림을 생성한다. 따라서 원본 데이터를 변경하지 않는다.
  • 스트림은 내부 반복자를 사용하므로 병렬 처리가 쉽고, 개발자는 요소 처리 코드에만 집중할 수 있게 된다.
  • 스트림은 컬렉션의 요소에 대해 중간 처리와 최종 처리를 수행할 수 있다.

 

스트림을 사용할 때 주의해야 할 점은?

  • 스트림을 재사용하지 않게 주의해야 한다.
  • 스트림은 for-loop보다 성능이 느리다.
  • 스트림을 이용하면서 람다 또는 메소드 참조를 사용하는 경우에는 지역 변수에 접근할 수 없다.
  • 스트림의 동작 순서를 고려하지 않으면 불필요한 연산 횟수를 증가시킬 수 있다.

 

 

 

 

 

 

 

 

 

관련 포스팅

[기술면접] JAVA - 1/4
[기술면접] JAVA - 2/4 (주로 예외처리Exception)
[기술면접] JAVA - 3/4 (주로 컬렉션 프레임워크)
[기술면접] JAVA - 4/4

 

 

 


 

출처:

https://domean.tistory.com/199

https://velog.io/@mooh2jj/Garbage-Collection%EC%9D%98-Mark-and-Sweep-%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98

https://mangkyu.tistory.com/94
https://dev-coco.tistory.com/153
https://gyoogle.dev/blog/