본문 바로가기
CS/기술 면접 대비

[백엔드 기술 면접] #8 JAVA

by 경험의 가치 2024. 11. 23.

#8 JAVA

 

Q. JVM의 작동 원리에 대해 설명해보아라

A. JVM은 자바 애플리케이션을 실행하기 위한 가상 머신이다. JVM은 클래스 로더를 통해 클래스 파일을 메모리에 로드하고, 바이트코드를 실행 엔진이 해석하며, JIT 컴파일러가 성능을 최적화한다.

 

Q. Garbage Collection이 무엇이며, Java에서 주요 알고리즘에 대해 설명하라.

A. 프로그래밍에서 객체나 변수를 생성하면, 이들은 메모리를 점유하게 됩니다. 하지만 더 이상 사용되지 않거나 참조되지 않는 객체가 남아 있다면, 그들이 차지하는 메모리는 비효율적으로 낭비됩니다. 이러한 메모리 누수를 방지하기 위해 Garbage Collection이 필요합니다. 주요 알고리즘으로는 Mark and Sweep, Minor/Major GC, G1 GC 등이 있다.

 

Q. HashMap과 TreeMap의 차이점은 무엇인가?

A. HashMap은 해시를 사용하여 빠른 접근을 제공하고, TreeMap은 정렬된 순서로 저장하며 성능 차이가 있다.

 

Q. 자바에서 원시 타은 null을 허용하지 않지만, 참조 자료형은 null을 허용합니다. 이 차이에 대해서 설명해주세요.또한, Wrapper Class가 무엇인지도 같이 설명해주세요.

A. 기본 자료형인 int, float, boolean 등은 메모리에 직접 값을 저장하기 때문에 null을 가질 수 없습니다. 반면에, 참조 자료형은 객체의 메모리 주소를 저장하는 변수이므로, 객체가 생성되지 않거나 특정 객체를 참조하지 않을 때 null을 가질 수 있습니다. 이때, null은 참조가 없음을 나타냅니다. Wrapper클래스는 기본 자료형을 객체로 감싸는 클래스입니다. 예를 들어, int는 Integer, boolean은 Boolean과 같은 Wrapper클래스가 있습니다. 이들은 참조 자료형이기 때문에 기본 자료형과 달리 null을 가질 수 있습니다. 따라서, Wrapper 클래스를 사용하면 기본 자료형의 값을 객체로 다루면서 null도 허용할 수 있습니다.

 

Q. POJO란 무엇이며, 그 역할은 무엇인가요?

A. POJO는 "Plain Old Java Object"의 약자로, Java에서 복잡한 프레임워크나 라이브러리에 종속되지 않고 순수하게 데이터를 저장하고 관리하는 간단한 Java 객체를 말합니다. POJO는 특정 프레임워크나 라이브러리에 종속되지 않아 독립적으로 테스트하기 쉽다는 장점이 있습니다. 또한, 특정 기술과 환경에 종속되어 의존하게 된 자바 코드는 가독성이 떨어져서 유지보수에 어려움이 생기고 확장성이 떨어져 객체지향성을 일어간다는 단점이 있습니다. 이를 통해, 더 객체지향을 추구할 수 있습니다.

 

Q. ThreadLocal이란 무엇이며, 사용할 때 주의할 점은 무엇인가요?

A. ThreadLocal은 스레드별로 독립적인 변수를 가질 수 있게 해주는 클래스입니다. 각 스레드마다 고유한 값을 저장하므로, 주로 사용자 인증 정보나 트랜잭션 컨텍스트처럼 스레드 간에 공유되지 않아야 하는 데이터를 다룰 때 사용됩니다. 예를 들어, 웹 애플리케이션에서 여러 사용자가 동시에 요청을 보낼 때, 각 요청은 별도의 스레드에서 처리되며, ThreadLocal을 사용하면 명시적인 동기화 없이도 스레드별로 데이터를 독립적으로 관리할 수 있습니다.

하지만, ThreadLocal을 사용할 때는 메모리 누수에 주의해야 합니다. 스레드 풀 환경에서는 스레드가 재사용되기 때문에, ThreadLocal 변수에 저장된 값이 스레드가 종료되기 전까지 남아 있을 수 있습니다. 이를 방지하기 위해 작업이 완료되면 remove() 메서드를 호출하여 ThreadLocal 변수를 정리해야 합니다.

 

Q. JDK, JRE의 차이는 무엇인가요?

A. JDK는 개발 도구 세트, JRE는 실행 환경을 뜻합니다. JDK가 더 큰 개념입니다.

 

Q. Java에서 static이란 무엇인가요?

A. static 필드와 매서드는 객체에 소속된 멤버가 아니라 클래스에 고정된 멤버이다. 그렇기에 클래스 로더가 클래스를 로딩해서 메소드 메모리 영역에 적재할때 클래스별로 관리됩니다. 따라서 클래스의 로딩이 끝나는 즉시 바로 사용할 수 있다. 덕분에, static 필드 또는 매서드가 저장된 메모리는 모든 객체가 공유하며 하나의 멤버를 어디서든지 참조할 수 있는 장점이 있다.

하지만, GC의 관리 영역 밖에 존재하기 때문에 프로그램 종료시까지 메모리가 할당된 채로 존재합니다. 너무 남발하게 되면 시스템 성능에 악영향을 줄 수 있다.

 

Q. try-with-resource 문에 대해서 설명해보세요.

A. 개발자가 하나 이상의 리소스를 사용할 경우 반드시, finally 문에서 .close()를 해서 리소스를 닫아줬어야 했다. 하지만, finally 문에서 자원을 해제 시켜주더라도 자원 해제를 위한 중복 코드가 발생하기 때문에 소스 코드의 가독성을 해치는 단점이 있었다.

따라서, try 블록에 괄호()를 추가하여 파일을 열거나 자원을 할당하는 명령문을 명시하면, 해당 try 블록이 끝나자마자 자동으로 파일을 닫거나 할당된 자원을 해제하는 try-with-resource 문법이 Java 7 버전부터 추가되었다.

 

Q. 동일성과 동등성의 차이를 설명해주세요. (equals와 ==의 차이는 무엇인가요?)

A. 동일성은 객체의 주소를 비교하는 것이고, 동등성은 객체의 같음을 비교하는 것이다.

기본적으로 자바에서는 Object 클래스에 정의된 equals() 메소드가 동일성 비교를 한다. 따라서, 개발자는 원한다면 equals() 메소드를 오버라이딩해서 동등성의 판단 기준을 정의해주면 된다.

 

Q. Java에서 Map을 사용할 때, 찾는 키가 존재하지 않는다면 기본 값을 반환하기 위해 쓰는 메서드가 무엇인가요?

A. getOrDefault() 쓰면 됩니다.

 

Q. Generic이 무엇인가요?

A. 자바 제네릭(Generic)은 클래스나 메서드를 작성할 때 데이터 타입을 미리 지정하지 않고, 사용할 때 타입을 지정할 수 있게 해주는 기능입니다. 제네릭을 사용하면 코드 재사용성이 높아지고, 컴파일 시 타입 안전성을 확보할 수 있습니다.

이러한 제네릭은 자료구조와 연관되어 있습니다. 자바의 List, Set, Map 등의 자료구조 클래스는 제네릭을 활용하여 다양한 타입의 데이터를 다룰 수 있도록 설계되어있습니다.

 

Q. 와일드카드에 대해서 설명해주세요.

A. 제네릭 와일드카드는 제네릭 타입에서 특정 타입을 명시하지 않고, 어떤 타입이 올 수 있는지에 대한 제약을 표현하는 방법입니다. 마치 정규 표현식에서 와일드카드 문자(*)가 어떤 문자든지 대표하는 것처럼, 제네릭에서도 와일드카드(?)가 다양한 타입을 나타냅니다. 와일드카드의 종류로는 비제한 와일드카드, 상한 제한 와일드카드, 하한 제한 와일드카드 가 있습니다.

 

Q. ConcurrentHashMap의 내부 구조와 사용 사례를 설명하시오.

A. ConcurrentHashMap은 스레드 안전한 해시맵 구현체로, 동시 접근 시 락을 줄여 성능을 개선한다.

 

Q. 내부 Class를 인스턴스가 아닌 static으로 선언하는 것이 권장되는 이유가 무엇인가요?

A. inner 클래스를 선언할때 static 키워드를 붙여주지 않으면 '외부 참조' 현상 때문에, 내부 클래스 인스턴스를 생성하기 위해 우선적으로 만들었던 외부 클래스 인스턴스가 정상적으로 GC 수거가 안되 메모리에 잔존하게 되어 문제점을 일으키게 된다. 따라서 내부 클래스가 외부 클래스의 멤버를 가져와 사용하는 경우가 아닌 경우 반드시 내부 클래스를 선언 할 때는 static 키워드를 붙여주어야한다.

 

Q. Java의 Stream이란 무엇이며, 사용하는 이유는 무엇인가요?

A. Java의 Stream은 컬렉션 데이터를 함수형 스타일로 처리하기 위한 도구입니다. Stream API를 사용하면 데이터를 필터링, 매핑, 집계 등의 연산을 통해 효율적으로 처리할 수 있습니다. Stream은 Java 8에서 도입된 기능으로, 기존의 루프 대신 map, filter, reduce 같은 함수형 메서드를 활용해 가독성을 높이고, 코드의 간결성을 제공합니다.

또한, Stream에서는 메서드 참조(::)를 사용할 수 있어 람다 표현식보다 간결하게 메서드를 호출할 수 있습니다. 예를 들어, stream.map(String::toUpperCase)는 각 요소를 대문자로 변환하는 작업을 쉽게 표현할 수 있습니다. parallelStream()을 통해 병렬 처리를 할 수 있어, 대규모 데이터를 처리할 때 성능을 개선할 수도 있습니다.

 

Q. Java의 CompletableFuture란 무엇이며, 사용하는 이유는 무엇인가요?

A. CompletableFuture는 Java에서 비동기 프로그래밍을 지원하는 클래스입니다. 이를 사용하면 비동기 작업을 수행하고, 완료된 후의 후속 작업을 구성할 수 있으며, 비동기 연산의 결과를 기다리거나 그 결과를 처리할 수 있습니다. CompletableFuture는 Java 8에서 도입되었으며, thenApply, thenAccept, thenCompose 등의 메서드를 통해 후속 작업을 구성할 수 있어 복잡한 비동기 워크플로우를 구현하는 데 유용합니다.

CompletableFuture는 비동기 작업을 쉽게 작성할 수 있고, join()이나 get()을 통해 결과를 기다릴 수 있으며, 에러 발생 시 예외 처리를 간편하게 할 수 있습니다. 또한, 여러 비동기 작업을 조합하거나, allOf와 anyOf 메서드를 통해 여러 작업을 동시에 실행할 수도 있습니다.

 

Q. Java에서 여러개의 String을 합칠 때 '+' 연산을 통한 문자열 합치기를 지양해야되는 이유는 무엇인가요?

A. Java에서 문자열을 + 연산으로 결합하는 경우, 컴파일러가 이를 최적화하여 StringBuilder를 사용하도록 변환해 줍니다. 즉, 컴파일 타임에 + 연산은 내부적으로 StringBuilder 객체를 생성하고, append 메서드를 사용하여 문자열을 결합한 후 최종적으로 toString()을 호출하는 형태로 변환됩니다. 그러나 반복문 내에서 문자열 결합을 할 때는 새로운 StringBuilder가 매번 생성되지 않도록 직접 StringBuilder를 사용하는 것이 더 효율적입니다.