Post

백엔드 면접 질문 모음

Java

JVM의 구조와 Java의 실행방식
  • 자바 가상 머신의 약자를 따서 줄여 부르는 용어
  • JVM의 역할은 자바 애플리케이션을 클래스 로더를 통해 읽어 자바 API와 함께 실행하는 것입니다.
  • 메모리 관리(GC)을 수행하며 스택기반의 가상머신입니다.
  • JVM의 구조는 Class Loader, Execution engine, Runtime Data Area, JNI, Native Method Library로 이루어져 있습니다.
    • 클래스 로더: JVM내로 클래스를 로드하고, 링크를 통해 배치하는 작업을 수행하는 모듈
    • 실행 엔진: 바이트 코드를 실행시키는 역할
      • 인터프리터: 바이트 코드를 한줄 씩 실행합니다.
      • JIT 컴파일러: 인터피르터 효율을 높이기 위한 컴파일러로 인터프리터가 반복되는 코드를 발견하면 JIT 컴파일러가 반복되는 코드를 네이티브 코드로 바꿔줍니다. 그 다음부터 인터프리터는 네이티브 코드로 컴파일된 코드를 바로 사용합니다.
      • GC(Garbage Collector): 가비지 컬렉터로 힙 영역에서 사용되지 않는 객체들을 제거하는 작업을 의미합니다.
    • Runtime Data Areas: 프로그램 실행 중에 사용되는 다양한 영역입니다.
      • PC Register: Thread가 시작될 때 생성되며 현재 수행 중인 JVM 명령의 주소를 갖고 있습니다.
      • Stack Area: 지역 변수, 파라미터 등이 생성되는 영역. 실제 객체는 Heap에 할당되고 해당 레퍼런스만 Stack에 저장됩니다.
      • Heap Area: 동적으로 생성된 오브젝트와 배열이 저장되는 곳으로 GC의 대상 영역입니다.
      • Method Area: 클래스 멤버 변수, 메소드 정보, Type 정보, Constant Pool, static, final 변수 등이 생성됩니다. 상수 풀(Constant Pool)은 모든 Symbolic Reference를 포함하고 있습니다.
    • JNI(Java Native Interface): 자바 애플리케이션에서 C, C++, 어셈블리어로 작성된 함수를 사용할 수 있는 방법을 제공해줍니다. Native 키워드를 사용하여 메서드를 호출합니다. 대표적인 메서드는 Thread의 currentThread()입니다.
    • Native Method Library: C, C++로 작성된 라이브러리 입니다.
  • Java의 실행방식
    • 자바 컴파일러(javac)가 자바 소스코드(.java)를 읽어 자바 바이트코드(.class)로 변환시킵니다.
    • Class Loader를 통해 class 파일들을 JVM으로 로딩합니다.
    • 로딩된 class파일들은 Execution engine을 통해 해석됩니다.
    • 해석된 바이트코드는 Runtime Data Areas 에 배치되어 실질적인 수행이 이루어집니다.
Garbage Collection

alt text

  • Heap 영역에서 사용하지 않는 객체들을 제거하는 작업
  • Java는 개발자가 메모리를 직접 해제해줄 수 없는 언어이기 때문에 해당 작업을 Garbage Collector가 담당
  • GC는 크게 Minor GC와 Major GC로 구분
    • Minor GC
      • Young Generation에서 발생하는 GC
      • Eden 영역이 가득 참에서 부터 GC 시작
      • GC 후 살아남은 객체는 Survivor 1 또는 Survivor 2로 옮겨짐
      • 이 두개의 영역 중 한 영역은 반드시 비어있어야 한다.(Survivor 1 <-> Survivor 2로 이동)
    • Major GC
      • Old Generation에서 발생하는 GC
      • Young 영역에서 오랫동안 살아남은 객체들은 Old 영역으로 이동
      • Eden 영역에서 바로 Old 영역으로 넘어가는 객체도 존재하는데, 이는 객체의 크기가 아주 클 경우 발생
    • Full GC: Young + Old + Permanent 전체 영역에서 발생하는 GC
  • Heap 메모리를 조정하는 것을 GC 튜닝이라고 함
오버라이딩과 오버로딩의 차이
  • 오버로딩
    • 같은 클래스 내에서 동일한 메소드 이름을 가지지만, 매개변수의 타입, 갯수를 다르게 구현하는 것
  • 오버라이딩
    • 상위 클래스의 메소드를 하위 클래스에서 재정의 하는 것
    • 매개변수의 타입, 갯수가 같다
    • @Override는 컴파일 시점에 오버라이딩에 대한 안정성을 부여해주기 때문에 반드시 써주는 것이 좋음
인터페이스와 추상클래스의 차이점
  • 추상클래스
    • 객체의 추상적인 상위 개념으로 공통된 개념을 표현할 때 사용
    • 단일 상속만 가능
  • 인터페이스
    • 구현 객체가 같은 동작을 한다는 것을 보장하기 위해 사용
    • 다중 상속이 가능
접근제어자의 종류

alt text

  • private
    • 같은 클래스 내에서만 접근 가능
  • default
    • 같은 패키지 내에서만 접근 가능
  • protected
    • 같은 패키지 또는 상속한 클래스 내에서만 접근 가능
  • public
    • 전체 영역에서 접근 가능
  • 접근제어자 사용 이유
    • 외부에 보여주고 싶은 정보들을 선택적으로 제공하기 위함 (캡슐화)
객체지향 프로그래밍
  • 프로그래밍에서 필요한 데이터를 추상화 시켜 상태와 행위를 가진 객체로 만들고, 객체들간의 상호작용을 통해 로직을 구성하는 프로그래밍 방법
  • 장점
    • 코드 재사용이 용이
    • 유지보수가 쉬움
    • 대형 프로젝트에 적합
  • 단점
    • 처리 속도가 상대적으로 느림
    • 객체가 많으면 용량이 커질 수 있음
    • 설계시 많은 시간과 노력이 필요
  • 객체 지향 프로그래밍 키워드 5가지
    • 클래스 + 인스턴스(객체)
    • 추상화
      • 구체적으로 정의하는 것이 아니라 필요한 정보만을 중심으로 간소화 하는 것
    • 캡슐화
      • 객체가 독립적인 역할을 할 수 있도록 데이터와 기능을 하나로 묶어서 관리하는 것
      • 실제로 구현되는 부분을 외부에 드러나지 않도록 하여 정보를 은닉할 수 있다.
    • 상속
      • 하나의 클래스가 가진 데이터나 기능을 다른 클래스가 그대로 물려받는 것
      • 기존 코드를 재사용하여 확장시킬 수 있다.
    • 다형성
      • 하나의 클래스나 메서드가 다양한 방식으로 동작이 가능한 것 (오버로딩, 오버라이딩)
객체지향 설계 원칙 5가지(SOLID)
  • 단일 책임 원칙(Single responsibility principle)

    • 한 클래스는 하나의 책임만 가지며 클래스는 그 책임을 완전히 캡슐화해야 함을 말한다.
  • 개방-폐쇄 원칙(Open/closed principle)

    • 소프트웨어 개체(클래스, 모듈, 함수 등)는 확장에 대해 열려 있어야 하고, 변경에 대해서는 닫혀 있어야 한다.
  • 리스코프 치환 원칙(Liskov substitution principle)

    • 프로그램의 객체는 프로그램의 정확성을 깨뜨리지 않으면서 하위 타입의 인스턴스로 바꿀 수 있어야 한다.
  • 인터페이스 분리 원칙 (Interface segregation principle)

    • 클라이언트가 자신이 이용하지 않는 메서드에 의존하지 않아야 한다. 큰 덩어리의 인터페이스를 구체적이고 작은 단위로 분리함으로써 클라이언트들이 꼭 필요한 메서드만 이용할 수 있게 한다.
  • 의존관계 역전 원칙(Dependency inversion principle)

    • 상위 모듈은 하위 모듈에 의존해서는 안 된다. 상위 모듈과 하위 모듈 모두 추상화에 의존해야 한다.
    • 추상화는 세부 사항에 의존해서는 안 된다. 세부 사항이 추상화에 의존해야 한다.
HashMap, LinkedHashMap, HashTable, TreeMap
  • HashMap
    • 검색, 삽입 시 시간복잡도 O(1)
    • 키 순서 무작위
    • 구현은 연결리스트로 이루어진 배열로 구현
    • null key와 null value를 모두 허용
  • LinkedHashMap
    • 검색, 삽입 시 시간복잡도 O(1)
    • 키 순서는 삽입한 순서대로 정렬 되어있다.
    • 구현은 양방향 연결 버킷으로 구현
  • TreeMap
    • 검색, 삽입 시 시간복잡도 O(logN)
    • 키의 순서는 정렬되어 있다. (즉, 키는 반드시 Comparable 인터페이스를 구현하고 있어야 한다.)
    • 구현은 레드-블랙 트리로 구현
  • HashTable
    • 검색, 삽입 시 시간복잡도 O(1)
    • 키 순서 무작위
    • 구현은 연결리스트로 이루어진 배열로 구현
    • null key와 null value를 모두 불허
    • 동기화를 지원한다 (thread safe)
      • 따라서 HashTable은 멀티스레드 환경에서 동작가능하며, 동기화를 지원하기 때문에 HashMap보다는 느리다.
      • 일반적으로 별다른 이유가 없으면 HashMap 을 사용하는 것이 좋다. 일반적으로 빠르고 오버헤드가 적기 때문
HashSet, TreeSet, LinkedHashSet
  • HashSet
    • 세트의 반복 순서를 보장하지 않거나 시간이 지나도 순서가 일정하게 유지된다.
    • 성능이 중요하고 요소의 순서가 중요하지 않은 경우 사용
  • TreeSet
    • 사용된 생성자에 따라 요소의 자연스러운 순서에 따라 또는 지정된 Comparator에 따라 반복된다.
    • 요소가 자연적인 순서를 사용하거나 Comparator에 의해 순서화되어야 할 때 사용
  • LinkedHashSet
    • 요소의 삽입 순서를 유지해야하는 경우 사용
Filter vs Interceptor
  • Filter alt text
    • J2EE 표준 스펙 기능
    • 디스패처 서블릿(Dispatcher Servlet)에 요청이 전달되기 전/후에 url 패턴에 맞는 모든 요청에 대해 부가작업을 처리할 수 있는 기능을 제공
    • 스프링 범위 밖에서 처리됨
      • 톰캣과 같은 웹컨테이너(서블릿 컨테이너)에 의해 관리됨 (스프링 bean으로 등록은 된다)
  • Interceptor alt text
    • J2EE 표준 스펙인 필터(Filter)와 달리 Spring이 제공하는 기술
    • 디스패처 서블릿(Dispatcher Servlet)이 컨트롤러를 호출하기 전과 후에 요청과 응답을 참조하거나 가공할 수 있는 기능을 제공
    • 웹 컨테이너(서블릿 컨테이너)에서 동작하는 필터와 달리 인터셉터는 스프링 컨텍스트에서 동작을 하는 것이다.
  • 요약 alt text
Spring의 의존성 주입 방법
  • 생성자 주입(Constructor Injection)

    • 생성자를 통하여 의존성을 주입하는 방법
    • @Autowired 생략 가능
    1
    2
    3
    4
    5
    6
    7
    8
    9
    
    @Controller
    public class TestController {
      //final을 붙일 수 있음
      private final TestService testService;
      //@Autowired
      public TestController(TestService testService) {
        this.testService = testService;
      }
    }
    
  • 필드 주입(Field Injection)

    • 필드에 @Autowired를 붙여서 주입하는 방식
    • 사용법이 매우 간단
    1
    2
    3
    4
    5
    6
    7
    
    @Controller
    public class TestController {
    
      @Autowired
      private TestService testService;
    
    }
    
  • 수정자 주입(Setter Injection)

    • setter 메소드에 @Autowired를 붙여서 주입하는 방식
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    
      @Controller
      public class TestController {
    
        private TestService testService;
    
        @Autowired
        public void setTestService(TestService testService) {
          this.testService = testService;
        }
      }
    
    
  • 생성자 주입이 권장되는 이유

    • 순환 참조 방지
      • 필드 주입, setter 주입은 bean이 생성된 이후에 참조를 하기 때문에 어플리케이션이 아무런 오류 없이 구동됨
      • 하지만 생성자 주입을 사용하면 BeanCurrentlyInCreationException이 발생하면서 어플리케이션 구동단계에서 순환 참조 에러를 발견할 수 있음
    • final 키워드를 이용한 불변성 보장 가능
    • 테스트에 용이함
This post is licensed under CC BY 4.0 by the author.