sol 개발 블로그 로고
Published on

[ASAC 스터디] Java

Authors
  • avatar
    Name
    Chan Sol OH
    Twitter

목차

[복합] JVM의 구조와 Java의 실행방식을 설명해주세요.

Java는 다른 언어들과 달리 코드를 컴파일도 하고 인터프리팅도 합니다. 먼저 java code를 byte code로 컴파일하고, .class 파일을 생성합니다. 이 .class 파일은 JVM에 의해서 인터프리트됩니다. JVM에 의해서 동일한 .class 파일은 어떤 플랫폼, 어떤 버전의 jvm에서든 동일하게 실행됩니다.

JVM은 크게 3가지 컴포넌트로 구성됩니다.

  1. Class Loader
  2. Runtime Memory/Data Area
  3. Execution Engine

Class Loader.class 파일의 byte code를 실행시키기위해 main 메모리로 옮깁니다. 가장 첫번째로 메모리로 옮겨지는 class는 main() 메서드를 포함하는 클래스입니다. Class Load는 다시 3가지로 구분됩니다. Linking 단계에서는 Class Loader가 메모리로 필요한 파일을 옮기면 의존성에 따라 각 요소들을 연결합니다. 마지막 Initialization 단계는 클래스나 인터페이스의 초기화 메서드를 실행하는 단계입니다. 이때 생성자, static 메서드 실행, static 변수 초기화를 수행합니다.

  • Bootstrap Class Loader
    • root class loader입니다.
    • 표준 Java package들과 .jar 파일을 메모리로 옮깁니다.
  • Extension Class Loader
    • 확장된 표준 라이브러리를 옮깁니다.
  • Application Class Loader
    • classPath에 표현된 파일들을 옮깁니다.

Runtime Data Area는 5가지 컴포넌트로 구성됩니다.

  1. Method Area : 필드 변수와 메서드의 정보가 저장되는 곳입니다. JVM이 시작될 때 생성되며, 한 JVM 당 한 Method Area를 가질 수 있습니다.
  2. Heap Area : 모든 객과 및 그와 관련된 인스턴스가 저장되는 곳입니다. 모든 클래스의 인스턴스나 배열이 할당됩니다. Method Area와 마찬가지로 한 Data Area를 가질 수 있고 모든 멀티 스레드가 접근할 수 있기 때문에 스레드 안전하지 않습니다.
  3. Stack Area : jvm에 스레드가 생성되면 runtime stack도 동시에 생성되며, 모든 지역 변수, 함수 호출, 결과물들이 저장됩니다. 만약 스레드가 사용할 수 있는 스택 크기를 넘어서 사용하려고 하면 StackOverflowError가 발생합니다. 모든 함수 호출이 있을 때마다 stack frame이 생성되는데 여기에는 함수의 지역 변수, Operand stack, 그리고 Frame Data가 저장됩니다. Operand stack은 메서드 내 계산을 위한 작업 공간이고, Frame data는 Constant Pool Resolution이라는 상수나 Method의 정상, 비정상 시 정보를 가지고 있습니다.
  4. Program Counter (PC) Registers : JVM은 멀티 스레드를 사용하는데 각 스레드는 PC 레지스터를 가집니다. 그리고 PC 레지스터에는 스레드가 실행 중인 JVM 명령어 주소를 가지고 있습니다. 따라서 명령어를 하나 실행하면 PC 레지스터는 다음 명령어 주소로 업데이트됩니다.
  5. Native Method Stacks : C나 C++ 같이 다른 언어로 작성된 메서드를 실행하는 영역입니다. 각 스레드마다 다른 스택을 가집니다.

Execution Engine는 3가지 컴포넌트로 구성됩니다.

  1. Interpreter : bytecode 명령어를 한 줄 한 줄 기계어로 번역하고 실행합니다. 인터프리팅의 단점으로 동일한 메서드를 여러번 실행해도 새롭게 해석을 수행해야합니다.
  2. JIT Compiler : interpreter의 단점을 극복하기 위해 interpreter가 동일한 메서드를 여러번 호출하면 JIT 컴파일러가 실행됩니다. JIT 컴파일러는 여러번 사용되는 bytecode를 기계어로 미리 번역시켜 놓고 함수가 호출되면 기계어를 바로 실행시킵니다.
  3. Garbage Collector : 힙 영역에서 참조되지 않는 객체를 제거합니다. GC는 사용되지 않는 메모리를 자동으로 청소합니다. Mark 단계에서는 사용되지 않는 객체를 찾고, Sweep 단계에서는 찾은 객체를 제거합니다.

GC는 MarkSweep을 위해 프로그램을 전부 멈추기 때문에 실시간성이 중요한 분야에는 적절하지 않습니다.

[복합] GC가 무엇인지, 필요한 이유는 무엇인지, 동작방식에 대해 설명해주세요.

Garbage Collector는 메모리의 힙 영역 공간을 차지하고 있는 참조 해제된 객체들의 영역을 다시 쓸 수 있게 해줍니다. 이를 위해 Mark Sweep 알고리즘을 사용합니다. 개발자는 객체를 수동으로 해제하지 않아도 자동화된 GC 덕분에 참조되지 않는 객체가 제거됩니다. 만약 참조가 되어 있더라도 오랬동안 사용되지 않는 객체가 있으면 GC가 제거하지 않기 때문에 메모리 누수가 발생합니다. 메모리 누수가 발생하면 원인을 찾기 쉽지 않기 때문에 개발자는 메모리 누수가 발생하지 않도록 조심해야합니다.

GC가 동작하는 방식

  1. 더 이상 사용되지 않는 객체(참조 해제된 객체)를 Collector가 스캔합니다.
  2. 스캔한 객체를 제거합니다.

공간 종류 GC의 동작을 알기 위해선 jvm에서 관리하는 공간의 종류를 알 필요가 있습니다.

  1. eden space : 객체가 생성되면 저장되는 공간입니다. 만약 eden이 꽉 차면, GC는 참조 해제된 객체를 지우거나 survivor space로 이동시킵니다.
  2. survivor space : heap의 young generation로 분류되는 공간입니다.
  3. tenured space : 생성된지 오래된 객체가 저장되는 공간입니다. 그리고 tenured는 eden 보다 더 크고 GC가 덜 관리합니다. 그리고 heap에서 old generation으로 분류됩니다.

young generation에서 동작하는 GC는 minor garbage collection이라고 부르고 old generation에서 동작하는 GC는 major garbage collection이라고 부릅니다. 왜냐하면 GC가 young generation에서 동작시간 보다 old generation에서 더 길게 동작하기 때문입니다. 하지만, GC가 동작하는 동안 pause times이 발생하고 애플리케이션이 완전히 멈춥니다.

제네릭에 대해서 설명해주세요.

제네릭은 파라미터화된 타입입니다. 이 타입은 methods, classes, 그리고 interfaces에 적용할 수 있습니다. Object는 모든 클래스의 슈퍼 클래스이고, 모든 클래스의 객체를 Object로 참조할 수 있습니다. 하지만, 이렇게 하면 타입 에러가 발생할 수 있기 때문에 제네릭으로 여기에 타입을 추가할 수 있습니다.

제네릭을 사용하면 로직이 같으나 타입이 달라서 여러 코드를 작성할 필요가 없어서 코드 재사용성이 높아집니다. 제네릭을 사용하면 컴파일 시 타입을 검사하기 때문에 타입 안전성이 올라갑니다.

제네릭을 사용할 때 주로 적용하는 컨벤션이 있습니다. T – Type E – Element K – Key N – Number V – Value

오버라이딩과 오버로딩을 설명해주세요.

Overriding은 부모 클래스로부터 메서드를 상속 받아서 자녀 클래스에서 구현하거나 수정하는 것을 의미합니다. 이때 접근 제어자의 범위가 확장될 순 있지만 좁아지진 못합니다. 당연하게도 입출력 타입이 일치해야합니다. Overriding을 사용하는 이유는 부모 클래스의 동작을 확장하기 위해 사용합니다. 그렇기에 리스코프치환 법칙을 지키기 위해 접근 제어자나 입출력 형식을 맞추는 겁니다.

Overloading은 한 클래스 내에 동일한 이름의 메서드를 여러개 작성하는 겁니다. Overloading 덕분에 다형성을 이룰 수 있고 이름의 메서드에 다양한 파라미터를 입력할 수 있습니다. Overloading을 사용하는 조건은 메서드 이름이 같고 타입 형태와 파라미터의 개수가 다르면 됩니다. 그리고 접근 제어자도 다양하게 할 수 있지만, 접근 제어자만 다르다고 Overloading이 되진 않습니다.

인터페이스와 추상클래스의 차이점에 대해 설명해주세요.

추상클래스는 일반 클래스와 같이 맴버변수와 메서드를 갖습니다. 그리고 추상 메서드를 가지는데 이건 입출력 타입을 정의한 메서드 선언부만 갖습니다. 인터페이스는 맴버변수가 없지만, 추상 메서드와 기본 메서드를 가집니다. 기본 메서드는 선언부가 아닌 구현체를 직접 제공하기 때문에 잘못하면 런타임 에러가 발생할 수 있습니다.

추상 메서드를 가진 클래스를 사용하려면 상속이나 구현을 통해서 해당 추상 메서드를 구현해야합니다. 그리고 인터페이스의 추상 메서드의 접근 제어자는 모두 public입니다.

추상클래스는 클래스를 확장하기 위해 사용하고 인터페이스는 구현한 객체들의 동일한 사용법과 동작을 보장하기 위해 사용합니다.

[복합] 자바 코드와 메서드, static 등은 메모리의 어디에 위치할까요?

java의 모든 메서드와 static 변수는 jvm 속 Runtime Data Area의 Heap Area에 저장됩니다. 그리고 java의 bytecode는 클래스 로더에의해 jvm의 Method Area에 저장됩니다.

자바의 원시타입들은 무엇이 있으며 각각 몇 바이트를 차지하나요?

정수 자료형 byte, short, int, long 각각 1,2,4,8 byte 크기를 가집니다. 정수 값을 그냥 적으면 기본 정수형인 int형으로 적용됩니다.

실수 자료형 float, bouble 각각 4,8 byte 크기를 가집니다. 실수 값을 그냥 적으면 기본 실수형인 bouble형으로 적용됩니다.

문자 char는 2byte 크기를 가지며 16-bit 유니코드 문자를 저장합니다. C에서 사용하는

접근 제어자의 종류와 이에 대해 설명해주세요.

  1. public : 어느 인스턴스나 필드나 메서드에 접근할 수 있습니다.
  2. protected : 같은 패키지나 해당 클래스를 상속 받은 클래스에서 사용할 수 있습니다.
  3. package-private : 아무런 접근 제어자를 적지 않으면 적용되는 것으로, 같은 패키지 내에 누구나 접근할 수 있습니다.
  4. private : 필드나 메서드를 가지고 있는 클래스 내에서만 접근 가능합니다.

[토론] 객체지향에 대해서 설명해주세요.

SOLID(객체지향 5대원칙)에 대해서 설명해주세요.

객체지향 프로그램 익히기(1)를 참고해주세요.

동일성(identity)와 동등성(equality)에 대해 설명해주세요. (equals(), ==)

동일성은 두 참조 타입 변수가 같은 메모리 주소를 가리키는 것을 의미합니다. 즉 변수만 다를뿐 같은 객체입니다. 동등성은 두 객체의 메모리 주소가 다르지만, 이 같은 것을 의미합니다.

원시타입과 참조타입의 차이에 대해 설명해주세요.

원시타입은 변수가 저장된 스택 영역에 변수의 값이 저장돼있지만, 참조타입은 변수가 저장된 스택 영역에 참조하는 메모리의 주소가 저장되고 실제 값은 해당 메모리에 저장돼있습니다.

String, StringBuilder, StringBuffer 각각의 차이에 대해 설명해주세요.

String은 불변으로 문자열을 저장하는 객체로 문자열을 늘리기 위해 + 연산자를 사용하면 새로운 객체를 만들기 때문에 속도에서 느립니다. StringBuffer와 StringBuilder는 문자열을 배열체 추가하는 느낌으로 문자열을 수정할 수 있습니다. 또한 처음 생성될 때 기본 버퍼 크기인 16개가 있고 길이가 부족하면 더 늘리기 때문에 시간적이 손해를 보기 싫다면 처음부터 충분한 길이의 버퍼를 쓰는게 좋습니다. 차이점은 StringBuffer는 스레드 안전하다는 점과 StringBuilder는 속도가 조금 빠르다는 점이 있습니다.

javac의 최적화로 String을 +로 연결하는 것은 내부적으로 StringBuilder를 쓰지만, 빈번한 + 연결은 처음부터 StringBuilder를 쓰는 것보다 비효율적일 것입니다.

Checked Exception과 Unchecked Exception에 대해 설명해주세요. 스프링 트랜잭션 추상화에서 rollback 대상은 무엇일까요?

Checked Exception은 컴파일 시점에 컴파일러가 확인하는 예외로, 무조건 try, catch or throw 구문으로 예외를 처리하거나 메서드를 호출한 쪽으로 던져야합니다.

Unchecked Exception은 런타임 시점에 발생하는 예외로, 예외처리를 강제하진 않지만 컴파일 시 예외를 확인할 수 없어서 예상치 못한 예외가 프로그램 실행 중에 발생할 수 있습니다.

보통 개발자가 코드 상으로 복구 가능한 예외 조건은 Checked Exception을 사용하고, 프로그래밍 오류에 대해선 Unchecked Exception을 사용합니다. 또한 호출하는 쪽에서 복구하는 예외는 Checked Exception을 사용해야합니다.

Spring Transaction은 기본적으로 Unchecked Exception이나 Runtime Exception을 상속한 예외를 던졌을 때 롤백합니다. 하지만, Transactional 어노테이션에서 rollbackFor이란 속성에 롤백시킬 Checked Exception을 작성하면됩니다. 그리고 Unchecked Exception도 try-catch문에서 별다른 예외를 던지지 않고 그냥 처리해버린다면, 롤백이 일어나지 않습니다.

[복합] Java8에서 추가된 기능에 대해서 설명해주세요.

Stream API

JAVA stream을 참고해주세요.

Stream api를 사용하는 이유는 외부 반복 없이 컬랙션에 대한 작업을 수행할 수 있습니다. 중요한 점은 중간 연산과 최종 연산입니다. 중간 연산은 데이터스트림을 필터링, 조작, 새로운 스트림을 생성하는 역할을 가지고 최종 연산이 있을 때까지 작업을 미룹니다. 최종 연산은 데이터스트림을 정리하거나 스트림이 아닌 다른 데이터 구조로 재구조화하는 역할을 가지고 더 이상 연산을 진행할 수 없습니다.

중간 연산으로 sorted, distinct, map 같은 연산이 있고, 또 다른 중간 연산을 붙일 수 있습니다. 최종 연산으로 forEach, toArray, toList, reduce 같은 연산이 있습니다.

Lambda

람다를 사용하는 이유는 순수함수를 생성하기 위함이고, 순수함수는 함수를 파라미터나 리턴값으로 사용할 수 있는 겁니다. 메서드와 달리 람다는 이름이 필요하지 않습니다. 자바는 클래스 안에 메서드가 필요하기 때문에 내부적으로 함수형 인터페이스를 이용합니다.

Optional

Optional 클래스는 null이 가능하지 않은 객체를 Nullable하게 바꿉니다. NullPointerException 예외를 발생하지 않도록 할 수 있습니다.

직렬화와 역직렬화에 대해서 설명해주세요.

객체의 데이터를 저장하거나 통신하기 쉬운 형식 (json, xml)로 바꾸는 것을 직렬화라고 하고, 반대를 역직렬화라고 합니다.

직렬화는 java의 객체를 디스크나 네트워크 장비가 이해할 수 없기 때문에 바이너리 형태로 변환합니다.