HyunsooZo's TIL logo HyunsooZo's TIL

자바

자바의 특징에 대해 설명해주세요.

자바는 객체 지향 프로그래밍 언어로 캡슐화,상속,다형성,추상화와 같은 객체 지향 원리를 제공하여 코드의 재사용성과 유지보수성을 높여줍니다.
또한 자바는 JVM 위에서 실행되기 때문에 OS에 종속적이지 않고, 독립적으로 실행될 수 있습니다.
클래스 로더에 의해 동적 로딩을 지원하여, 실행 시 모든 객체를 생성하는 것이 아니라, 필요한 시점에 클래스를 생성함으로서 메모리를 효율적으로 사용할 수 있습니다.
캡슐화 사용 시 외부에서 직접 접근을 제한하고, 오직 정해진 메서드를 통해서만 데이터 조작을 가능하게 끔 하여, 코드 수정 시 해당 객체 내부만 변경하면 됨
상속은 기존 클래스의 속성이나 메서드를 새로운 클래스가 물려받아 사용. (중복 코드 작성 필요 x)
다형성 : 여러 형태로 동작하는 객체를 하나의 인터페이스나 클래스로 표현할 수 있게 해줌. 시스템 확장이나 변경시 기존 코드를 수정하지 않고도, 새로운 형태의 객체 쉽게 추가
추상화 : 사용자가 복잡한 내부 구현을 몰라도, 재공되는 인터페이스를 통해 필요한 기능 쉽게 사용 가능

Java의 장단점에 대해 설명해주세요.

자바는 JVM 위에서 작동하기 때문에 OS에 종속적이지 않고, 독립적으로 실행될 수 있습니다.(다양한 환경에서 같은 코드 재사용 가능)
객체지향 언어로 설계되어 코드의 재사용성 과 유지보수성이 좋습니다.(이유 : 캡상다추)
추가로, 자바는 클래스 로더를 통해 동적 로딩을 지원하는데, 이는 (실행 시)필요한 시점에만 특정 클래스나 자원을 로딩하여 메모리 효율성을 높이게 됩니다.
하지만 자바의 경우 소스코드가 바이트 코드로 변환 된 후, JVM에 의해 다시 기계어로 번역되는 과정을 거치기에, 일반적으로 한번만 컴파일링을 거치는 언어에 비해 실행속도가 상대적으로 느릴 수 있습니다.

Java 8의 특징은 무엇인가요?

첫번째로, 람다 표현식(화살표)이 도입되어 함수형 프로그래밍을 구현하는 것이 가능해졌습니다. 두번째로는 Stream API(대용량 처리시 유용)를 도입하여 데이터 컬렉션을 다루는데 있어 효율적인 처리할 수 있는 병렬 및 순차처리 방법을 제공합니다.
그리고 인터페이스에 default 메서드를 통해 인터페이스 안에서 공통으로 사용되는 함수를 미리 정의할 수 있게 되었습니다.
마지막으로 Optional 클래스가 추가되었는데, null 값을 안전하게 포장하는 래퍼 클래스로써, 이를 통해 참조 시 NPE를 방지할 수 있게 설계되어있습니다.


참고 - 작은 데이터 세트에 대해서는 for 문이 더 빠를 수 있다(내부적으로 많은 오버헤드를 가지고 있기떄문)
오버헤드 - 작업을 하기 위한 추가적인 리소스나 비용(ex: 추가적인 연산, 메모리 사용, 시간 등)
기본적으로 for문은 단일 스레드에서 순차적으로 데이터를 처리하는 반면 Stream API 사용시에는 데이터 처리를 멀티 스레드(스레드 풀에서 남는 스레드)로 병렬화할수있다.
for문은 기본적으로 단일스레드에서 순차적으로 작동하지만, 병렬처리 할수있음(별도의 멀티스레딩 코드 작성해야 ), 하지만 Stream API는 간단한 메서드 호출 한번(parallelStream())으로 병렬처리 쉽게 가능

참고 - 자바 8,11,17이 LTS(Long Term Support)버전임. 이는 3년 단위로 유지보수를 지원해주는 버전
참고 - 자바 11에 추가된것(String, File 클래스에 메서드 추가, 람다에서 var 키워드 사용 가능, HttpClient라는 것이 추가됨)

Java의 main 메소드가 static인 이유에 대해 알고 계시나요? (public static void main(){String args[]})

static 멤버는 프로그램 시작 시, 클래스 로더에 의해 메모리에 로드되어 인스턴스를 생성하지 않아도 호출이 가능하기 떄문입니다.
런타임 데이터 영역(JVM 메모리 영역)에서 Method Area라고 불리는 영역에는 static 키워드를 가진 변수 및 메서드가 생성됩니다.
따라서 JVM은 별도로 인스턴스를 생성하지 않아도, Method Area에 로드된 main()을 실행할 수 있습니다.
public인 이유
JVM이 프로그램을 실행할때 외부에서 이 메서드에 접근하여 호출해야 하기 떄문에 public으로 선언되어야 함(어디에서나 접근 가능하게, 다른 접근제어자의 경우 에러)
static영역에 할당되기에 Garbage Collector에 의해 해제되지 않고 프로그램 종료 시까지 할당된 채로 남아있는다

절차지향, 객체지향, 함수형 프로그래밍이란 무엇이고 차이점은 무엇인가요?

절차지향 프로그래밍이란, 프로그램을 일련의 순차적인 절차 또는 단계를 따라 데이터에 작업을 수행하는 방식을 말합니다.(ex: C언어)
이는 컴퓨터의 처리구조와 유사하기 때문에 실행 속도가 빠르지만 유지보수가 어렵다는 단점(엄격하게 순서가 정해져 있어서)이있습니다.
객체지향 프로그래밍은 프로그래밍에서 필요한 데이터를 추상화시켜 상태와 행위를 가진 객체를 만들고, 그 객체들간의 유기적인 상호작용을 통해 로직을 구성하는 프로그래밍 방법입니다(ex: 자바,파이썬).
이는 앞서 설명드린 절차지향형 프로그래밍 방식에 비해 코드 재사용이 용이하고, 유지 보수가 쉽지만 처리 속도가 상대적으로 느리고 설계시 많은 시간과 노력이 필요합니다.
함수형 프로그래밍은 자료 처리를 수학적 함수의 계산으로 취급하고, 가변 데이터를 멀리하는 프로그래밍 패러다임의 하나로 대표적인 언어로는 SQL,Scala등이 있습니다.

객체 지향형 프로그래밍(OOP)가 무엇이죠??

객체 지향 프로그래밍은 컴퓨터 프로그래밍 패러다임 중 하나로, 프로그래밍에서 필요한 데이터를 추상화시켜 상태(필드,변수)와 행위(메서드)를 가지는 객체를 만들고, 그 객체들간의 유기적인 상호작용을 통해 로직을 구성하는 프로그래밍 방식입니다.
객체지향 탄생 이유 -> 코드의 유연성을 위해


꼬리질문1 - 장단점에는 무엇이 있죠?
우선 객체들이 재사용 가능한 컴포넌트(자체로 기능을 수행할 수 있는 모듈, 보통 인터페이스로 다른 컴포넌트와 상호작용)로 설계될 수 있어, 같은 기능을 다시 구현할 필요 없이 해당 객체를 재사용하거나 상속받아 확장하여 사용할 수 있습니다.
그리고 객체 단위로 코드가 구분되어 있어, 특정 기능에 문제가 발생하거나 변경이 필요할때 해당 객체만을 수정하면 되므로 유지 보수 과정을 간소화할 수 있습니다.
또한 데이터는 객체 내부에 캡슐화되어, 외부에서 직접 접근할 수 없습니다. 이때 데이터 접근은 객체가 제공하는 메소드를 통해서만 가능하므로, 데이터를 안전하게 보호할 수 있습니다.
하지만 설계 단계에서 상당한 시간이 걸릴수 있고 객체 간의 메세지 전달이나 상속등의 오버헤드 때문에 절차 지향 프로그래밍에 비해 처리 속도가 느립니다.
또한 객체가 상태를 가지고 있기에, 이러한 상태 변경의 예측(객체 메서드에 의해 변경)이 어렵고 디버깅하기 어렵게 만들 수 있습니다.
만약 특정 상태에 종속적인 경우, 그 객체는 상태에 따라 다르게 작동하므로 재사용이 어려울 수도 있습니다.

꼬리질문 2 - 객체 지향 프로그래밍의 특징에 대해 말해주세요.
추상화는 객체의 공통적인 속성과 기능을 추출하여 정의하는 것을 의미합니다.
다른 말로 표현하자면 인터페이스에 추상 메서드나 상수를 통해 어떤 객체가 수행해야 할 핵심적인 역할만 규정해두고, 실제적인 구현은 해당 인터페이스를 구현하는 각각의 객체에서 하도록 프로그램을 설계하는 것입니다. 이를 통해 인터페이스에 정의한 역할을 각각의 클래스의 맥락에 맞게 구현하게 되고, 이를 통해 변경에 유연한 프로그래밍을 할 수 있습니다

상속은 한 클래스의 속성과 메서드를 다른 클래스가 물려받아 사용하는 것입니다.
상속을 통해 새로운 클래스는 부모 클래스의 특성과 메소드를 재사용할 수 있으며, 필요에 따라 확장(추가적 기능 넣거나) 및 수정(아예 기능 변경?)을 할 수 있습니다.

다형성은 하나의 클래스나 메서드가 맥락에 따라 다른 역할을 수행할수 있는 의미합니다
예를 들어, 메소드가 같은 이름을 가지지만, 입력 매개변수에 따라 다르게 동작하는 오버로딩, 또는 같은 이름의 메소드(동물 울다 -> 강아지 멍멍, 고양이 야옹)가 슈퍼클래스와 서브클래스에서 다른 행동을 하는 오버라이딩 등이 이에 해당합니다.
좀더 객체 지향적인 관점에서 다형성을 다루어보자면 상위 클래스의 타입의 참조변수로 하위 클래스의 객체를 참조할 수 있도록 하는 것입니다

캡슐화는 객체의 속성과 메서드를 하나로 묶고 일부를 외부에 감추어 은닉하는 것을 말합니다.이를 통해 내부 데이터는 외부의 임의 접근으로 부터 보호될 수 있습니다.
또한 캡슐화를 통해 클래스의 내부 구조나 동작 방식에 대해 깊게 알 필요 없이, 제공되는 메서드를 통해 클래스를 쉽게 활용할 수 있습니다.(결합도도 낮춤)
꼬리질문 3 - getter, setter를 사용하는 이유가 무엇인가요?? (어짜피 private으로 멤버 변수 지정하고 public으로 getter, setter 메서드를 만들어왔는데 왜그럴까??) 우선 private으로 멤버변수를 선언하면 외부에서 직접 접근할 수 없기에, 멤버 변수 값이 예상치 못한 방식으로 변경되는 것을 막을 수 있습니다.
대신 public으로 선언된 getter와 setter 메서드를 통해 멤버 변수에 접근하게 되는데, 이 메서드들에 값의 변경을 제어하고 검증하는 로직을 추가할수 있습니다.
예를들어 setter 메서드 내에서 입력된 값을 검증하여 객체의 상태가 유효한 상태로 유지되도록 할 수 있습니다.(ex: setAge가 0보다 작다면 에러나 로그를 남김)

꼬리질문 4 - 캡슐화에 대해 좀더 자세히 물어보는 경우
캡슐화 목적 - 앞서 말한 외부 에서직접 변경 X 말고 다른 것도 있음(밑에)
캡슐화는 단지 getter setter를 사용해 private 멤버에 접근하도록 하는것 이상의 의미를 가지는데 이는 객체의 내부 구현을 감추고, 사용자에게는 필요한 인터페이스만을 제공한다는 것입니다.
이를 통해 클래스의 내부 데이터를 보호할 수 있고, 사용자는 클래스가 제공하는 메서드를 통해서만 클래스와 상호작용할 수 있습니다.
예를들어 사용자가 자동차라는 객체를 사용하고, 이러한 자동체 객체는 달린다,멈춘다,방향을 바꾼다 등의 메서드를 제공한다고 가정해보겠습니다.
이러한 메서드는 사용자에게 필요한 인터페이스입니다.
하지만 이때 자동차 객체의 내부 구현(엔진이 어떻게 돌아가는지, 브레이크가 어떻게 동작하는지)은 감추어져있습니다.
이러한 내부 구현은 사용자가 알 필요가 없을 정보일 뿐 아니라 바꾸지 않아야할 정보입니다.
이렇게 내부의 복잡한 부분을 감추고, 사용자에게는 단순한 인터페이스만을 제공하는 것이 캡슐화의 핵심입니다.

참고 - 캡슐화를 하는 목적은 객체의 내부 구현을 감추어 내부 구현의 변화가 일어나더라도 협력하는 외부 객체에 변화의 영향이 퍼져나가지 않도록 막기 위함이다.

SOLID 원칙에 대해 설명해주세요

SOLID는 좋은 객체 지향 설계를 위한 5가지 원칙을 다룬 개념입니다.(단개리인의)

우선 단일 책임 원칙(Single Responsibility Principle)은 한 클래스가 하나의 책임만을 가지도록 하는 원칙입니다.
이때 저는 중요한 기준을 변경이라고 보는데, 해당 클래스에 대한 변경이 있을때 다른 클래스에 파급 효과가 적다면 단일 책임을 잘 따른것이라 볼 수 있습니다.

개방 폐쇄 원칙은 소프트웨어 구성 요소(클래스,모듈, 함수 등)가 확장에는 열려 있어야 하며, 변경에는 닫혀있어야 함을 의미합니다.
즉 새로운 변경 사항이 발생했을때(ex: 기능 추가) 직접적인 (객체) 수정 없이도 이를 반영할 수 있도록 설계해야 함을 의미합니다.

리스코프 치환 원칙은 부모 클래스(또는 베이스 타입)의 인스턴스를 자식 클래스(또는 서브타입)의 인스턴스로 교체하는 과정에서 프로그램의 정확성이 유지되어야 한다는 원칙입니다.
인터페이스 분리 원칙(ISP)은 특정 클라이언트를 위한 인터페이스 여러개가 범용 인터페이스 하나보다 나음을 의미합니다.
마지막으로 의존관계 역전 원칙(DIP)은 프로그래머가 구체적인 것보다 추상화된 것에 의존해야 함을 의미합니다.


SRP 원칙을 준수하면 각 클래스가 맡은 책임이 명확해져, 한 책임의 변경이 다른 책임에 미치는 영향을 최소화 할 수 있습니다.
OCP 준수 하기 위해 스프링에서는 DI(의존성 주입)과 제어의 역전(IOC)를 제공합니다.


리스코프 치환 원칙(LSP) 예시
클라이언트는 인터페이스를 구현한 객체의 내부 구현을 알지 못하므로, 객체는 인터페이스의 명세를 준수하여 구현되어야 합니다.
예를들어 자동차 인터페이스에 앞으로 가는 기능을 선언했는데, 서브 클래스에서 뒤로 가게 구현한 것은 LSP를 위반한 예입니다. 느리더라도 앞으로 가는 기능을 제공해야 LSP를 준수한 것입니다.

예를 들어, ‘전자기기’라는 인터페이스에 ‘켜기’, ‘끄기’, ‘충전하기’, ‘인쇄하기’라는 메소드가 있다면, ‘스마트폰’ 클래스는 ‘인쇄하기’ 메소드를 사용하지 않을 것입니다. 이럴 때 ‘스마트폰’을 위한 ‘켜기’, ‘끄기’, ‘충전하기’ 메소드만을 가진 인터페이스와 ‘인쇄하기’ 메소드를 가진 인터페이스로 분리하는 것이 더 효과적일 수 있습니다. 이렇게 하면 각 클래스는 필요한 메소드만을 포함한 인터페이스에 의존하게 되므로 더 명확하고 깔끔한 설계를 할 수 있습니다.

DIP 예시
즉, 클라이언트가 구현 객체에 직접 의존하는 것이 아니라, 클라이언트와 구현 객체 모두 인터페이스에 의존해야 합니다. 이렇게 되면 구현 객체가 변경되더라도 클라이언트에는 아무런 변화가 없게 됩니다.

DIP 예시
예를 들어, 회사에서 서비스를 운영하면서 MySQL에서 PostgreSQL로 데이터베이스를 바꾸려고 합니다. 이 때, 웹 애플리케이션의 각 부분이 구체적으로 MySQL에 의존하고 있다면, 이 변경은 코드의 모든 부분을 수정해야 하는 매우 큰 작업이 될 것입니다. 또한, 이러한 변경은 버그를 만들 가능성이 크기 때문에 위험성도 큽니다.
하지만 만약 웹 애플리케이션의 각 부분이 데이터베이스에 직접 의존하는 대신, 데이터베이스 인터페이스(DB Interface)에 의존하도록 설계되어 있다면 이 문제는 간단히 해결될 수 있습니다.
이 인터페이스는 MySQL과 PostgreSQL 모두에서 사용할 수 있는 일반적인 메소드(예: get, set, update, delete 등)를 정의합니다. 웹 애플리케이션의 나머지 부분은 이 인터페이스를 통해 데이터베이스와 상호작용하므로, 어떤 데이터베이스가 뒤에 연결되어 있더라도 그것에 영향을 받지 않습니다.
따라서 MySQL에서 PostgreSQL로 데이터베이스를 바꿀 때는 단지 인터페이스를 구현하는 클래스만 바꾸면 됩니다. 웹 애플리케이션의 나머지 부분은 이 변경을 알아차리지 못하고 그대로 작동합니다. 이러한 접근 방식은 의존관계 역전 원칙을 따르는 것이며, 시스템의 유연성과 확장성을 크게 향상시킵니다.

OCP 예시
스프링을 기반으로 한 예시를 정리해보겠습니다:

  1. MemberService 클래스는 MemberRepository 인터페이스에 의존하고 있습니다.
  2. MemberRepository 인터페이스에는 여러 구현체가 있습니다.
  3. 만약 MemberService 클래스 안에서 MemberRepository의 구현체를 직접 생성한다면(MemberRepository m = new MemoryMemberRepository()), 구현체 변경 시에 MemberService 코드도 수정해야 합니다. 이렇게 되면 OCP와 DIP(의존성 역전 원칙)를 위반하게 됩니다.
  4. 따라서, 의존성 주입(Dependency Injection, DI)을 통해 이 문제를 해결할 수 있습니다. 구현체를 외부에서 주입받도록 수정함으로써, MemberService 클래스는 구현체에 대한 의존성을 줄이고, 변경에 닫혀 있게 됩니다.

    요약하면, OCP는 기능의 확장과 변경에 대응하기 위한 원칙입니다. 스프링 예시에서는 의존성 주입을 통해 OCP를 준수하고, 기존 코드의 변경을 최소화하며 유지 보수성을 높이는 것을 보여줍니다.
클래스와 객체가 무엇이죠?.

클래스는 객체를 생성하기 위한 설계도라고 생각할 수 있습니다.
클래스는 객체가 가져야할 상태를 나타내는 필드(field)와 객체가 수행할 수 있는 동작을 나타내는 메서드로 구성됩니다.
객체는 클래스에서 정의된 내용에 따라 메모리에 할당된 실체를 의미합니다. 각각의 객체는 자신만의 상태를 가지며, 클래스에 정의된 동작을 수행할 수 있습니다.

상속이랑 컴포지션의 차이에 대해 설명해주세요.

상속(IS-A)은 한 클래스의 속성과 메서드를 다른 클래스가 물려받아 사용하는 것입니다.
(보통 모듈을 만들때는 응집도는 높게, 결합도는 낮게하며, 변경사항이 있을때는 클라이언트의 코드만 변경하는 것이 이상적입니다).
이때 부모 클래스를 상속받는 하위 클래스는 상위 클래스의 구현 내용 변경에 따라 하위 클래스의 구현 내용도 바뀔 수 있습니다.
즉 자식 클래스는 부모 클래스에 대한 결합도가 높습니다.
따라서 이러한 문제를 해결해보기 위해 등장한 개념이 컴포지션입니다.
컴포지션이란 상속 처럼 기존의 클래스를 확장하는 것이 아닌, private 필드로 기존 클래스의 인스턴스를 참조하는 방식(forwarding)입니다.
즉 기존 클래스가 새로운 클래스의 구성요소가 되는것입니다. (HAS-A 관계)

꼬리질문 - 어떠한 경우 상속을 쓰고 어떠한 경우에 컴포지션을 쓰면 좋을까요???
상속은 부모 클래스의 코드를 재사용하고자 할때, 또는 확장을 통해 새로운 기능을 추가하고 싶을 때 사용합니다.
하지만 이는 부모 클래스 변경이 자식 클래스의 영향을 미치므로, 부모 클래스가 변경될 가능성이 적거나, 부모 클래스를 잘 아는 상황에서 사용해야합니다.
컴포지션은 유연성이 필요한 상황에서 사용되며, 컴포넌트를 독립적으로 변경하거나 확장하는 경우에 효과적입니다. 또한 상속에 대한 위험성을 피하고 싶을 때 사용하면 좋습니다.

참고 - 부모 클래스의 private 멤버는 상속에서 제외

super() 메서드란 무엇인가요??

super() 메서드는 자식 클래스에서 부모 클래스의 생성자를 호출할때 사용됩니다.(자식 클래스 생성자에서만 호출할 수 있음)
기본적으로, 모든 클래스의 생성자 첫 번째 줄에는 super()가 숨겨져 있어 명시적으로 호출하지 않아도 부모 클래스의 기본 생성자를 호출하게 됩니다.
하지만, 부모의 생성자 중 매개변수 가 있는 생성자가 실행되도록 할려면 super()에 반드시 매개변수를 사용 후 호출해야 합니다.

transient에 대해 아시나요???

transient는 직렬화 과정에서 특정 필드를 제외하고 직렬화(특정 변수에 붙이는 단위, Serializable 대상에서 제외) 하도록 지정하는데 사용합니다.
transient으로 선언된 필드는 직렬화 과정에서 제외되며, 기본값으로 초기화됩니다.따라서 transient 필드는 직렬화 되지 않고, 역직렬화 과정에서 해당 필드는 기본값으로 설정됩니다.
이는 패스워드와 같이 보안상 중요한 변수나 꼭 저장해야 할 필요가 없는 변수에 대해서는 transient를 사용할 수 있습니다.

직렬화란 객체를 바이트 스트림으로 변환하여 저장하거나 네트워크 상에서 전송하기 위한 과정

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

직렬화란 자바 시스템 내부에서 사용되는 객체 또는 데이터를 외부의 자바 시스템에서도 사용할 수 있도록 바이트 형태로 데이터를 변환하는 기술입니다.
역직렬화는 직렬화의 반대 과정으로 바이트 스트림을 원래의 객체 상태로 복원하는 과정을 말합니다.

참고 - 자바에서는 Serializable이라는 인터페이스를 구현하여 생성(클래스가 implements Serializable 상속 함으로서 직렬화 될수 있는 형태로 만들어짐) 참고 - 자바에서 입출력을 수행할려면, 즉 어느 한쪽에서 다른 쪽으로 데이터를 전달할려면 두 대상을 연결하고 데이터를 전송할 수 있는 무언가가 필요한데 이를 Stream이라고 부름.
즉 데이터를 운반하는데 사용하는 통로
참고 - 직렬화의 경우 -> (여기 여러 이름들 있음)OutputStream 객체 이용, 역직렬화의 경우 InputStream 객체 사용

꼬리질문 - SerialVersion UID 에 대해 아시나요?
SerialVersionUID는 자바에서 객체 직렬화를 수행할때 클래스의 버전을 식별하는데 사용하는 유니크한 ID입니다.
이를 통해 직렬화된 객체의 클래스가 현재 JVM에서 사용하는 클래스와 동일한지 확인할 수 있습니다.
만약 클래스가 변경되었지만, SerialVersionUID가 동일하다면, JVM은 변경된 클래스도 이전 클래스와 같다고 간주하고 역직렬화를 수행합니다.
그러나 이 아이디가 다르면, JVM은 역직렬화 과정에서 예외를 발생시킵니다.

try-catch-finally의 단점과, 이로 인해 나온 구문에 대해 알고 계신다면 설명해주세요.

try-catch-finally 구문에서 리소스를 생성하게 되면, 생성은 try에서 하고 반납은 finally에서 하다보니 실수의 발생 여지가 있었습니다.
그래서 나온 구문으로 try-with-resources가 있습니다. try 옆에 괄호로 리소스를 생성해주면, 따로 반납 코드를 작성하지 않아도 자동으로 리소스를 반납합니다.

volatile에 대해 아시나요???

volatile은 변수의 값을 메인메모리에서 항상 값을 읽거나 씀으로서 스레드 간의 가시성을 보장하는데 사용됩니다. 이를 붙인 변수는 여러 스레드에서 동시에 접근할 수 있고, 해당 변수의 값을 읽거나 쓸 때 항상 메인 메모리에서 직접 읽고 쓰도록 보장됩니다.
이는 스레드 간의 변경 사항이 즉시 반영되고 다른 스레드에서 최신 값을 읽을 수 있도록합니다.

꼬리 질문 1 - volatile을 사용하는 주요한 경우는 어떤 경우가 있을까요??
여러 스레드에서 공유하는 변수의 가시성을 보장하고 최신값을 읽기 위해 사용합니다.

꼬리질문 2 - (멀티스레드 용 용어임)가시성이 무엇이고, volatile을 사용하지 않으면 어떤 경우 문제가 발생할 수 있죠??

가시성이란 한 스레드가 변수를 변경했을 때 이 변경사항이 다른 스레드에게 얼마나 빨리 보이는지를 의미하는 용어입니다
메모리 가시성 문제는 주로 각 스레드가 자신의 로컬 메모리(예: CPU 캐시)에 변수 값을 저장하고 사용하기 때문에 발생합니다

예를들어 한 스레드에서는 플래그를 true로, 다른 스레드는 그 플래그가 true가 될때까지 대기하는 상황이라고 해보겠습니다.
만약 이 플래그가 volatile로 선언되지 않으면, 플래그를 설정하는 스레드가 그 값을 true로 변경했음에도 불구하고 대기 중인 스레드는 여전히 false를 볼 수 있습니다. 왜냐하면 각 스레드는 자신의 CPU 캐시에서 값을 읽고 쓰기 때문입니다. 이렇게 되면 대기 중인 스레드는 영원히 대기 상태에 머물수 있습니다.

기본형과 참조형이 무엇인지와 그에 대한 차이를 설명해주세요.

자바에서는 총 8가지의 기본형 타입(byte, short, int, long(이까지 정수형), float, double, char, boolean)을 미리 정의하고 제공해줍니다.
기본형 변수는 메모리의 스택 영역에 할당되며, 값이 저장될때 해당 메모리 위치에 직접 저장됩니다.
참조형 타입에는 기본형을 제외한 모든 타입으로 클래스, 인터페이스, 배열 등이 있습니다. 참조형 변수는 메모리의 스택 영역에 할당되며, 실제 데이터(객체)는 힙 영역에 할당됩니다.
이때 스택 영역에 저장된 참조형 변수는 힙 영역에 저장된 객체의 주소를 가리키고 있는 것이 특징입니다.

참고 - 8가지 이외엔 전부 참조형
기본 타입의 경우 크기가 작고 고정적이기에 Stack 영역에 저장하고, 참조 타입은 데이터의 크기가 가변적이고 동적이므로 Heap 영역에 저장
참조형의 경우 String과 배열은 new 없이 생성가능하지만 참조타입이고, 더이상 참조 하는 변수가 없을때 GC에 의해서 삭제됨

자바에서 '=='연산자와 'equals()' 메서드의 차이에 대해 설명해주세요

== 은 참조 비교로 두 객체가 같은 메모리 공간을 가리키는지 확인하고, equals는 두 객체의 내부 값이 같은지 내용을 비교합니다.
참고: equals는 override 할 수 있기 때문에 사용자가 원하는 논리적인 통일성을 비교할 수 있다.
참고 : equals는 주소 값을 자체를 비교하는 것은 아니지만 주소값을 통해서 비교를 함, equals는 기본 타입에 대해서는 적용 불가

call by reference 와 call by value의 차이에 대해 설명해주세요.

call by value와 call by reference는 함수 호출 시 인자 전달 방식의 차이를 나타냅니다.
call by value는 함수 호출 시 인자로 전달되는 변수의 값을 복사하여 함수의 인자로 전달 합니다. 따라서 함수 내에서 인자 값이 변경되더라도 원래 변수에는 영향을 미치지 않습니다.

반면 call by reference는 함수 호출 시 인자로 전달되는 변수의 레퍼런스(주소)를 전달한다.
이 경우 함수 내에서 인자 값이 변경되면 원래 변수의 값도 함께 변경됩니다.

자바에서는 기본형 변수를 함수의 인자로 전달할 때 call by value 방식을 사용합니다. 참조형 변수를 전달할 때도 사실 call by value 방식을 사용하지만, 이때 전달되는 값은 객체의 참조 주소입니다. 따라서 함수 내에서 해당 주소의 객체를 변경하면 호출자에게도 그 영향이 반영됩니다. 하지만, 새로운 객체를 할당하는 등 참조 자체를 변경하는 경우 호출자의 원본 참조에는 영향을 주지 않습니다.

클래스 변수, 지역 변수, 인스턴스 변수의 차이에 대해 설명해주세요

클래스 변수는 static 키워드를 사용해 선언되며, 클래스 레벨에서 정의가 됩니다. 이는 해당 클래스의 모든 객체가 공유하며, JVM 의해 클래스가 로드될때 메모리(Method Area)에 할당됩니다.(객체를 생성하지 않고도 클래스 이름을 통해 접근 가능)
인스턴스 변수는 객체가 생성될 때마다 힙(heap) 영역에 매번 새로 생성되어 각 객체마다 독립적인 값을 가지게 됩니다.
지역변수는 메서드나 블록 내에서 선언되며, 해당 영역 내에서만 사용할 수 있습니다. 이는 스택 영역에 할당되며, 메서드나 블록 실행이 종료되면 메모리에서 해제됩니다.(초기화 하지 않으면 사용 불가)

참고 : 클래스 변수, 인스턴스 변수 둘다 멤버 변수
참고 : 인스턴스 변수는 객체가 생성될 때마다 힙(heap) 영역에 매번 새로 생성되며, 각 객체마다 독립적인 변수를 가지게 됩니다.
지역 변수는 메서드가 호출될 때마다 스택(stack) 영역에 새로 생성되고 메서드 호출이 종료되면 소멸합니다.
반면에 클래스 변수는 static Area에 한 번만 생성되어 해당 클래스의 모든 객체가 공유합니다.
멤버변수는 지역 변수와 다르게 각 타입의 기본 값으로 자동 초기화
참고 : 자바를 실행했다고 바로 메모리에 올라가는 것이 아님, 클래스 로드 시점은 다음과같다

  1. 클래스 인스턴스를 생성하려할때
  2. 클래스의 정적 멤버에 접근하려할떄!
    (중요!!)
  3. 클래스를 직접 로드하는 경우 Class.forName()


    public class test {

    int iv; // 인스턴스 변수
    static int cv; // 클래스 변수

    void method() {
    int lv; // 지역 변수
    }
    }
상속이란 무엇인가요?

상속이란 부모 클래스가 가지고 있는 상수, 메소드를 자식 클래스에서 물려 받아 같이 공유하면서 확장하는 것입니다
상위 클래스를 상속 받게 되면 하위 클래스, 즉 상속받은 클래스는 상위 클래스의 변수나 메서드를 사용할 수 있게 됩니다.
이게 가능한 이유는 하위 클래스 생성 시 상위 클래스의 생성자가 먼저 호출되고 하위 클래스의 생성자가 호출되도록 프로그램 내부에 이미 설정되어 있기 때문입니다.
즉 하위 클래스 생성자에서 super()를 자동으로 호출하게 됩니다.
이러한 상속에는 예약어 extends가 사용됩니다.

정적 타입의 언어와 동적 타입 언어의 차이는 무엇인가요??

타입(자료형)의 결정을 컴파일 할때 결정한다면 정적타입 언어(Java,C), 런타임 과정에서 결정한다면 동적 타입의 언어입니다.
정적 타입의 언어의 경우 컴파일 때 미리 타입을 결정하기 떄문에, 실행 속도가 빠르고 타입 에러로 인한 문제점을 초기에 발견 할 수 있어 타입의 안전성을 높일 수 있습니다.
하지만, 매번 코드 작성시 변수형을 결정해줘야 하는 번거로움이 있습니다.
동적 타입의 언어의 경우 런타임까지 타입의 결정을 끌고 갈 수 있기에 유연성이 높고, 컴파일 시 타입을 명시해주지 않아도 되기에 빠르게 코드를 작성할 수 있습니다.
하지만 런타임에서 타입 관련 에러를 만날 수 있으므로, 해당 에러들에 대한 주의가 필요합니다

컬렉션(collection) 클래스에서 제네릭을 사용하는 이유가 무엇인가요?

컬렉션 클래스에서 제네릭을 사용하면 컴파일러는 특정 타입만 포함 될 수 있도록 컬렉션을 제한하게됩니다. 컬렉션 클래스에 저장하는 인스턴스 타입을 제한하여 런타임에 발생할수있는 잠재적인 모든 예외를 컴파일 타임에 잡아낼 수 있도록 도와줍니다.

스크립트 언어와 컴파일 언어의 차이에 대해 설명해주시고 자바는 컴파일언어인가요? 스크립트 언어인가요?

컴파일 언어는 코드를 작성한 후 전체 코드를 기계어로 번역하는 컴파일 과정을 거칩니다. 이 컴파일된 코드는 실행 파일로 저장되어, 이후에는 컴파일 과정없이 빠르게 실행할 수 있습니다(대표적으로 C,C++)
컴파일 언어의 장점으로는 실행 속도가 빠르다는 것(사전에 컴파일 되어 기계어 상태로 실행되므로)과, 컴파일러가 코드의 문법적 오류를 미리 확인해준다는 점입니다.
이에 반해 스크립트 언어는 컴파일 단계 없이, 실행 단계에서 한줄씩 기계어로 번역 후, 실행되므로 통상 컴파일 언어보다 실행이 느립니다.
이를 위해 인터프리터라는 프로그램이 필요하며, 코드는 실행될떄마다 인터프리터에 의해 해석됩니다(python, javascript)
스크립트 언어의 장점으로는 개발 속도가 빠르고, 코드 수정 후 바로 결과를 확인할 수 있다는 점이있습니다.

자바의 경우에는 두가지의 특성을 모두 가지고 있습니다. 자바 코드는 먼저 자바 컴파일러에 의해 바이트 코드로 컴파일됩니다.
그러나 이 바이트 코드는 기게어가 아니라 자바 가상 머신이 이해할 수 있는 중간언어입니다. 이 바이트 코드를 실행할 떄는 JVM의 인터프리터가 한 줄 씩 읽어 나가며 실행합니다.
자바 코드가 처음 실행될때는 인터프리터가 바이트 코드를 한줄씩 해석하며 실행하지만, 자주 실행되는 코드 부분은 JIT 컴파일러가 기계어로 컴파일하여 효율적으로 실행합니다.

묵시적 형변환과 명시적 형변환의 차이가 뭔가요?

묵시적 형변환은 프로그래머가 직접 형변환을 지정하지 않아도 컴파일러가 자동으로 형변환을 수행하는 것을 말합니다.
이는 보통 작은 데이터 타입에서 큰 데이터 타입으로 변환될 때 발생되며, 정보의 손실이 없습니다.
예를들어 int 형값을 double 형 변수에 저장할려고 할때 묵시적 형변환이 일어납니다.
반면 명시적 형변환은 프로그래머가 직접 형변환을 지시하는 것을 말합니다.
보통 큰 데이터 타입에서 작은 데이터 타입으로 변환될때 사용하며, 이 경우 데이터 손실이 발생할 수 있습니다.
이때 형변환하려는 타입을 괄호 안에 명시하여 사용하게 됩니다.

예시 int myInt = 10; double myDouble = myInt; // 자동으로 int형이 double형으로 형변환 됩니다. double myDouble = 10.5; int myInt = (int) myDouble; // 명시적으로 double형을 int형으로 형변환 합니다. 여기서 소수점 아래는 손실됩니다.

오버로딩과 오버라이딩에 대해 설명해주세요

오버로딩은 같은 이름의 메서드를 여러 개 정의하되, 매개변수의 개수나 타입이 다르게 하는 것입니다. 이를 통해 다양한 매개변수를 받을 수 있는 메서드를 구현할 수 있습니다. 반면에 오버라이딩은 상속 관계에서 자식 클래스가 부모 클래스가 정의한 메서드를 재정의하는 것입니다. 메서드의 시그니처는 동일하게 유지되지만, 구현 내용을 변경하여 자식 클래스의 요구에 맞게 동작할 수 있습니다. 이를 통해 다형성을 구현할 수 있습니다.
참고 : 다형성이란 한 타입의 참조 변수로 여러 타입의 객체를 참조할 수 있도록 하는 것!

접근 제어자에 대해 설명해주세요


접근 제어자란 멤버 또는 클래스에 사용되어, 해당하는 멤버 또는 클래스를 외부에서 접근하지 못하도록 제한하는 역할을 합니다.
이러한 접근제어자에는 private, default, protected, public이 있습니다.
private의 경우에는 같은 클래스내에서만 접근이 가능하고, default의 경우에는 같은 클래스와 패키지 내에서만 접근이 가능합니다.
protected의 경우에는 같은 패키지 뿐 아니라, 그리고 다른 패키지의 자손클래스에서 접근이 가능하고 public의 경우에는 접근 제한이 없습니다.

static 예약어에 대해 설명해주세요

자바에서 static은 클래스 레벨에서 공유되는 멤버(변수, 메서드)를 선언하는데 사용되는 예약어로, 이 키워드를 사용해 변수, 메서드, 내부 클래스를 정의할 수 있습니다.
static 변수는 클래스의 모든 인스턴스가 공유하는 변수로 클래스 로드 시 메모리에 한번 할당되고, 인스턴스 생성과 무관하게 사용 가능합니다.
static 메서드는 클레스 레벨에서 동작하는 메서드로, 인스턴스 생성없이 호출 할 수 있습니다.
static 내부 클래스는 클래스 내부에 정의된 정적 클래스로, 외부 클래스와 독립적으로 동작합니다.따라서 외부 클래스의 인스턴스 생성과 상관없이 사용할 수 있습니다.

참고 : 보통 유틸리티 메서드를 포함하는 클래스에 사용, static 으로 선언된 변수나 메서드는 JVM의 메서드 영역에 위치

참고 : static 키워드를 통해 생성된 정적 멤버는 Metaspace에 저장되고, 저장된 메모리는 모든 객체가 공유해서 하나의 멤버를 어디에서든 참조할 수 있음
하지만 GC의 관리 영역 밖이기에 프로그램 종료시까지 메모리에 할당된 채로 존재해서, 너무 남발하면 시스템에 악영향

final 예약어에 대해 설명해주세요(finally, finalize 물어볼수도)

final 예약어는 값이 변하지 않는 상수를 선언할때나, 메서드의 오버라이드 및 클래스 확장을 방지하기 위해 주로 사용됩니다.
변수에 final을 붙이면 해당 변수는 변경 될수 없는 상수가 되고, 메서드에 final이 붙으면 이는 오버라이드가 불가능해집니다.
또한 클래스에 final을 붙으면 다른 클래스에서 이를 상속할 수 없습니다.

finally 키워드는 try catch 블록 뒤에서 항상 실행될 코드 블록을 정의하기 위해 사용
finalize 메서드는 가비지 컬렉터가 더 이상의 참조가 존재하지 않는 객체를 메모리에서 삭제하겠다고 결정하는 순간 호출된다.

추상 클래스에 대해 설명해주세요

추상클래스는, abstract 키워드를 사용해 선언한 클래스로, 추상 메소드를 최소 한 개 이상 포함한 클래스입니다.여기서 추상메소드는, abstract 키워드를 사용해 원형만 선언되고 내부 코드는 작성하지 않은 메서드를 뜻합니다.
추상클래스 내부에 추상메소드 외의 다른 것들도 추가가 가능하다는 것이 특징이고, 추상클래스의 사용 주 목적은 관련성이 높은 클래스 간의 코드를 공유하고 확장하고 싶은 목적입니다
이러한 추상 클래스를 상속받은 클래스는 추상 메서드를 모두 구현하던가, 본인도 추상 클래스로 만들어야 오류가 발생하지 않으며 예약어 abstract를 사용합니다.

참고 : extends 사용
참고 : abstract 안 붙여도 구현부가 없는 메서드는 추상 메서드로 분류함
참고 : 추상 클래스는 일반 메서드도 가질 수 있음, 그 자체로 인스턴스화 될수 없음
참고 : 새로운 클래스를 작성할때 일종의 설계도 역할을 해줌, 공통 기능을 상속해 주기 위한 용도

// 추상 클래스 선언 abstract class 동물 {

// 일반 메서드
void 숨쉬기() {
System.out.println(“숨을 쉽니다.”);
}

// 추상 메서드
abstract void 소리내기(); // 구현부가 없습니다.
}

인터페이스에 대해 설명해주세요

인터페이스는, interface 키워드를 사용해 선언하며 default와 static을 제외하고는 추상 메소드와 상수만을 포함합니다
interface 내부의 모든 메소드는 추상 메소드로, abstract public이 생략되어 있는 상태입니다.상수 필드는 public static final이 생략되어 있습니다.
인터페이스는 다중 상속이 가능하며 관련성이 없는 클래스들의 논리적으로 같은 기능을 자신에 맞게 구현을 강제하는데에 목적을 갖습니다.



사용 목적에 대해 간단히 말씀드리자면 우선 클래스들이 가져야 할 메서드의 시그니처(public abstract 자동 붙음)를 정의함으로서, 클래스가 구현해야 하는 행동을 지정할 수 있습니다.
또한 인터페이스를 구현하는 여러 클래스는 동일한 인터페이스 메서드를 사용하여 다양한 구현(새의 move, 고양이의 move)을 제공할 수 있습니다.
이를 통해 구현체에 의존하지 않고 인터페이스에 의존하게 되어 유연성과 확장성이 향상됩니다.(Bird 클래스나 Cat 클래스에 의존하는게 아니라 Animal 이라는 인터페이스에 의존)


참고 : 추상 클래스는 추상 메서드를 가진 클래스이고, 인터페이스(implements)는 추상 메서드로만 이루어진 클래스
참고 : 인터페이스, 추상클래스 둘의 메서드는 상속되어야 하기 때문에 final 키워드 붙지 않음

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


짧게 외우려면 그냥 이걸로 답변하자.
정리하자면, 구조상 차이로 추상 클래스는 abstract 키워드가 붙고 추상 메서드뿐만 아니라 다른 변수, 메서드도 선언이 가능하고, 인터페이스는 추상 메서드와 상수만 선언 가능하다.(자바 8부터는 default 메서드 선언 가능)
목적의 차이로 추상 클래스는 관련성이 높은 클래스 간의 코드 공유(재사용)와 확장을 목적으로 하고, 인터페이스는 관련성이 없는 클래스들의 같은 기능을 자신에 맞게 구현하는데 목적이 있다.


추상 클래스는 상속받은 클래스가 기능을 이용 및 확장하는 것이고, 인터페이스는 하위클래스에게 일종의 설계도를 제공하는 것입니다.
추상 클래스 상속과 같이 extends 관계를 가진 경우에는 자식이 부모의 필드나 메서드를 참조할 수 있기 때문에, 자식이 부모의 기능을 확장한 구조로 이루어집니다.
따라서 부모-자식 관계 뿐 아니라, 같은 부모에서 확장된 자식 클래스들끼리도 어느정도 역할에 공통점이 존재해야 합니다.
반면 인터페이스의 구현체의 경우, 자식들간에 전혀 아무런 관계가 없더라도 인터페이스로 관계를 맺을 수 있습니다.
요약하자면 관련성이 높은 클래스 간의 코드 공유 및 확장을 목적으로 하면 추상클래스, 관련성이 없는 클래스들의 같은 기능을 자신에 맞게 구현하고자 한다면 인터페이스를 사용하는 것이 바람직합니다.
참고 - 둘 다 서로 하위 클래스에게 공통적으로 사용하는 방식을 뽑아 전달하는 객체들인 공통점
그러나 목적이 다르다. 추상 클래스는 추상 메서드를 자식 클레스가 구체화해서 기능 확장에 목적이 있지만, 인터페이스는 서로 관련이 없는 클래스에게 공통적으로 사용하는 방식이 필요하지만 기능을 각각 모두 반드시 구현케 하는 경우에 사용한다.

Error와 Exception의 차이에 대해 설명해주세요

Error(모든 에러는 Unchecked)는 시스템 레벨에서 발생하는 문제로 개발자가 처리할 수 없는 심각한 오류를 말합니다.(OutOfMemory, StackOverflow)
이런 경우 일반적으로 애플리케이션 코드에서 복구할 수 없는 문제들이기에 대부분의 경우 애플리케이션이 멈추게 됩니다.
Exception은 프로그램의 정상적인 흐름을 방해하는 조건을 나타냅니다.
이러한 예외는 체크예외와 unchecked 예외로 나뉩니다.
일반적으로 컴파일 단계에서 명확하게 Exception 체크가 가능한 것을 Checked Exception이라 하며, 실행 과정 중 발견되는 Exception을 Unchecked Exception이라고 합니다

Checked예외와 UnChecked 예외의 차이에 대해 설명해주세요

둘의 가장 명확한 구분 기준은 꼭 처리를 해야 하느냐 입니다.
Checked Exception이 발생할 가능성이 있는 메서드라면 반드시 오류 처리(try/catch or throw : 이건 의존성 문제 야기할수도)를 해주어야 합니다.
반면 Unchecked Exception은 명시적인 예외처리를 하지 않아도 됩니다.
또한 예외를 확인하는 시점에서도 구분할 수 있습니다.
일반적으로 컴파일 단계에서 명확하게 Exception 체크가 가능한 것을 Checked Exception이라 하며, 실행 과정 중 발견되는 Exception을 Unchecked Exception이라고 합니다.
그리고 트랜잭션 roll-back 여부에도 차이가 있습니다.
기본적으로 Checked Exception은 예외가 발생하면 기본적으로 트랜잭션을 roll-back하지 않고 예외를 던져주는 반면, Unchecked Exception은 예외 발생 시 트랜잭션을 roll-back한다는 점에서 차이가 있습니다.</br>
- 참고 : 롤백이란 트랜잭션 내에서 발생한 모든 변경 작업을 취소하고 이전 상태로 돌리는 과정(데이터 일관성 유지)

  • 대표적인 Checked : Exception , UnChecked : RuntimeException
문자열을 리터럴(string = "abcd")로 할당하는 것과 객체(string = new String("abcd"))로 할당하는 방식의 차이가 무엇인가요?

문자열을 리터럴과 객체로 할당하는 방식의 주요 차이점은 메모리 위치와 효율성에 있습니다.
리터럴로 선언된 문자열은 Heap의 String pool 영역에 저장되는 반면, new 키워드를 사용해 생성된 문자열 객체는 힙 영역에 저장됩니다.
String Pool에서는 동일한 문자열 리터럴에 대해 하나의 참조만 유지하여 중복된 저장을 피하게 되는 반면 new를 사용하면 같은 내용의 문자열이라도 매번 새로운 객체가 생성되어 메모리 사용이 비 효율적이게 됩니다.
참고 - 굳이 객체로 만들어 GC 대상이 되는것 보다, String pool로 만들어 활용하는 것이 메모리 효율 측면에서 더욱 유리할 것으로 보임

자바에서는 왜 String을 불변 객체로 만들었을까요?

String이 불변하다는 것은 여러 참조 변수가 하나의 String 객체를 가리키고 있을때도 그 객체를 조작할 수 없다는 것을 의미합니다.
이를 통해 실수나, 버그로 인한 데이터 변조의 위험에서 자유로울 수 있습니다.
자바에서는 String pool이라는 메모리 영역을 활용해 문자열을 관리합니다. 이곳에는 한번 생성된 문자열들이 저장되며, 동일한 문자열에 대해서는 하나의 참조만을 유지하게 하여 메모리의 효율성을 높입니다.
만약 String이 불변성을 가지지 않았다면, 같은 문자열을 참조하는 여러 변수 중 하나에서 문자열을 변경하게 되면, 그 변경이 String pool의 해당 문자열에도 반영되어,다른 모든 변수의 참조한 값도 변경되게 됩니다.

참고
이거 뿐 아니라 불변 객체기 떄문에 두 문자열을 더할때 값을 저장할 새로운 문자열 객체를 만들어야 한다는 단점, 또한 동시성 제어 없이도 안전함
String이 불변인 이유 -> 내부에 char[] value 배열이 final임!

String str1 = “hello” , String str2 = “hello”;
System.out.println(str1);
System.out.println(str2);

str1.toUpperCase(); // -> 만약 String이 불변성을 가지지 않았다면 str1을 변경할 수 있을것

System.out.println(str1); System.out.println(str2); // 만약 바뀌었다면 둘다 출력이 hello가 될것!

StringBuffer, StringBuilder 차이는 무엇이죠?

StringBuilder와 StringBuffer는 모두 문자열을 변경하거나 추가하는 연산을 제공하는 가변성 클래스입니다
이 둘을 가르는 차이점은 동기화에 있습니다
StringBuffer는 각 메서드에 대한 동기화를 지원하므로 멀티쓰레드 환경에서 안전합니다.
즉 여러 스레드가 동시에 객체를 변경할 수 없으므로, 멀티스레드 환경에서 사용하면 좋습니다
반면 StringBuilder는 동기화를 지원하지 않습니다. 하지만 단일 스레드 환경일 시 더 빠른 성능을 보입니다.

참고 - 문자열 끼리 더하기 할시, 실제로는 새로운 객체를 생성한다기 보단(반은 맞고 반은 틀림), 컴파일 전 내부적으로 STringBuilder 클래스를 만들어서 사용
즉, “hello” + “world” 문자열 연산이 있다면 이는 new StringBuilder(“hello”).append(“world”).toString() 과 같다는 말이다.
참고 - 소켓과 같은 비동기 상황에서는 Buffer 사용하기
참고 - StringBuilder와 StringBuffer는 문자열을 추가하거나 변경할 때, 새로운 객체를 생성하는 것이 아니라 기존 객체를 직접 변경합니다.
참고 - String은 불변이기에 멀티쓰레드 상황 생각할 필요 X

꼬리질문 - 왜 동기화가 걸려있으면 느린걸까요? 동기화란 여러 스레드가 동시에 특정 코드블록이나 메서드에 접근하는 것을 방지하여, 데이터 불일치 문제를 해결하는 기법입니다.
예를들어 두개의 스레드가 동시에 StringBuffer 객체를 수정하려고 시도하면, 동기화된 메서드는 한번의 하나의 스레드의 작업만을 허용합니다.
이로 인해 다른 스레드는 첫번째 스레드가 작업을 완료할때까지 기다려야하며, 이 동안 성능이 저하될수 있습니다.
이렇게 스레드가 다른 스레드의 작업을 완료하기를 기다리는 것을 블로킹이라고 합니다. 이러한 블로킹은 많은 시스템 리소스를 소비하고,
이 동안 CPU는 유휴상태가 될 수 있습니다.

왜 StringBuilder가 단일 스레드 환경에서 더 빠른 성능을 보이죠?

StringBuilder가 단일 스레드 환경에서 더 빠른 성능을 보이는 이유는 ‘동기화 오버헤드’ 때문입니다.
동기화는 멀티스레드 환경에서 데이터의 일관성을 유지하기 위해 필요한 작업이지만, 그 과정에서 추가적인 시스템 리소스를 소모합니다. 이를 ‘동기화 오버헤드’라고 합니다.
StringBuffer의 모든 주요 메소드는 동기화 되어 있습니다. 따라서 멀티스레드 환경에서는 안전하지만, 단일 스레드 환경에서는 필요 없는 동기화 비용이 발생하게 됩니다.
반면에 StringBuilder는 동기화를 지원하지 않습니다.
그래서 동기화에 따른 오버헤드 없이 작동하기 때문에, 단일 스레드 환경에서는 StringBuilder가 StringBuffer보다 빠른 성능을 보이게 됩니다.”

String, StringBuffer, StringBuilder 차이:star:

A)


String 객체는 한번 생성되면 할당 메모리 공간이 변하지 않습니다. 따라서 값이 변경되지 않습니다.
장점-불변이기때문에 단순 조회연산에서 타 클래스보다 빠르고, 불변이기때문에 동기화를 신경쓸 필요가 없습니다.
단점-문자열연산이 자주 일어나면 더이상 참조되지 않는 객체는 GC대상이 되며 이들이 쌓입니다.

StringBuffer, StringBuilder는 mutable(가변)입니다. 문자열 연산 시 메모리 크기를 변경시켜 문자열이 변경됩니다.
문자열 연산이 자주 있을 시 사용하는 것이 좋습니다.
둘 사이의 차이는 동기화 여부에 있습니다.
StringBuffer는 메서드마다 synchronized 예약어가 걸려있어 멀티스레드 환경에서 동기화를 지원합니다.
StringBuilder는 동기화를 보장하지 않습니다. 그 대신 연산처리가 StringBuffer보다 빠릅니다.
결론적으로 멀티스레드 환경에선 StringBuffer, 싱글스레드 환경에선 StringBuilder를 사용하는 것이 좋습니다.

래퍼 클래스란 무엇이죠?

래퍼 클래스란 자바의 기본형 데이터 타입을 객체로 다루기 위한 클래스를 의미합니다.
이러한 각각 기본 데이터 타입에 대응하는 래퍼 클래스가 있으며, 이를 통해 기본 데이터 타입도 객체처럼 다룰수 있습니다.

참고 - int : Integer, char : Character, 나머지는 기본 타입의 앞글자만 대문자

래퍼 클래스를 사용하는 이유엔 어떤것들이 있을까요?

java의 컬렉션 프레임워크는 객체만 저장할 수 있습니다. 따라서 기본 데이터 타입을 컬렉션에 저장하려면 해당 데이터 타입의 래퍼 클래스로 변환해야 합니다.
그리고 기본 데이터 타입은 null 값을 가질 수 없습니다. 그러나 특정 상황에서 변수가 아무런 값도 가지고 있지 않음을 나타내고 싶은 경우 래퍼클래스를 사용하여 null값을 할당할 수 있습니다.

참고: 기본 데이터 타입은 객체가 아니라서 직접 동기화를 수행할 수 없다.(객체만 java의 동기화 메커니즘을 사용할수 있기에..)
참고 : 일반적으로 객체의 경우 외부에서 함부로 값을 변경할 수 없음…(바꿀려면 아마 set? : 캡술화 반영)

Boxing과 UnBoxing이 무엇이죠?

박싱은 기본 타입의 데이터를 래퍼 클래스의 인스턴스로 변환하는 것이고
언박싱은 래퍼클래스의 인스턴스에 저장된 값을 기본 타입의 데이터로 변환하는 것입니다.

AutoBoxing과 UnBoxing이 무엇인지와 사용시 주의해야할점에 대해 설명해주세요.

오토 박싱이란 기본 데이터 타입의 값을 래퍼 클래스의 객체로 변환하는 과정입니다.
오토 언박싱은 래퍼 클래스의 객체가 그에 해당하는 기본데이터 타입의 값으로 변환되는 과정을 말합니다.
주의해야할 점으로 우선 성능상의 이슈가 있습니다.
AutoBoxing과 UnBoxing은 내부적으로 객체를 생성하거나, 참조를 변환해야 하는 과정을 거치므로 반복적인 연산이 필요한 경우 성능 저하를 일으킬 수 있습니다.
또한 AutoBoxing을 통해 래퍼 클래스의 객체를 생성할때 null을 할당할 경우, 이후 UnBoxing 시 NPE가 발생할 수 있습니다.
또한 == 로 값을 비교할때 값이 아닌 참조를 비교하게 되므로 이에 대해 주의해야 합니다.

Enum이란 무엇이죠?

Enum이란 특정 변수가 가질 수 있는 값의 집합을 미리 지정한 자료형입니다.(즉 변수가 특정한 값들 중 하나만 가질 수 있게 하려고 할떄)
Enum은 명시적으로 특정 값들만을 가질 수 있기에, 그 외의 값들을 가지려 하면 컴파일 오류가 발생합니다. 이로 인해 코드의 안정성을 높일 수 있습니다.
또한 enum의 각 상수는 해당 타입의 유일한 인스턴스를 나타내므로, 이를 통해 Singleton 패턴을 구현하는데 사용할수있습니다.
따라서 멀티쓰레드 상황에서 안전

참고 - ex)enum Day{Mon,Tue,Wed,Thurs,FRI,SAT,SUN) ~~~ , Day day = “not a day” // 컴파일 에러

프레임워크와 라이브러리의 차이에 대해 설명해주세요.

프레임워크란 소프트웨어 개발을 위해 만들어진 구조나 도구의 집합을 의미합니다.
개발자는 프레임워크의 규칙과 패턴에 따라 개발을 진행하며, 프레임워크가 제공하는 틀안에서 구현한 코드를 실행하고 확장할수 있습니다.
라이브러리는 소프트웨어 개발에 자주 쓰일만한 코드를 미리 구현해놓고, 필요한 곳에서 호출하여 사용 가능하도록 만들어진 집합을 뜻합니다
둘다 개발 생산성을 올리기 위한 도구라는 점에서 같지만, 이 둘은 흐름에 대한 제어의 권한이 어디있냐에 따라 차이가 있습니다.
즉 프레임워크는 전체적인 흐름을 자체적으로 가지고 있으며 프로그래머가 그 안에 필요한 코드를 작성하는 반면(IOC)
라이브러리는 사용자가 흐름에 대해 제어를 하며 필요한 상황에 가져다 쓰는 것입니다.

참고 - 프레임워크에는 라이브러리들이 포함되어있음(라이브러리가 프레임워크 구성), 라이브러리 하나가 모듈과 유사한 개념
참고 - 스프링에서 JPA는 라이브러리, 개발자는 필요한 경우 JPA를 선택하고, 기능을 사용하기 위해 해당 API 호출
참고 - 스프링 프레임워크 자체는 JPA 라이브러리 사용하는 것 이상을 제공, 전체적인 구조와 흐름을 제어하기 위해 IOC 컨테이너와 DI 기능 제공

equals()와 hashCode() 메서드가 무엇인지 설명해주세요

equals()는 객체의 내용이 같은지 비교할때 사용하는 메서드이고 hashCode()는 객체의 메모리 주소를 기반으로 고유한 정수값을 반환하는 메서드입니다(Heap에 저장된 객체의 메모리 주소, 두 객체가 동등한지 비교할때 주로 사용).
동일한 객체는 항상 동일한 메모리 주소를 가집니다. 결과로, 기본 hashCode()는 객체의 메모리 주소를 기반으로 하는 해시 코드를 반환합니다. 따라서 동일한 객체는 동일한 해시 코드를 가져야 합니다. 그러나 객체의 내용을 기반으로 equals() 메서드를 재정의(오버라이드)했을 때, 그 내용이 동일한 두 객체가 서로 다른 해시 코드를 반환하는 상황을 피하기 위해 hashCode() 메서드도 함께 재정의하는 것이 중요합니다. 이렇게 함으로써 동일한 내용을 가진 객체가 동일한 해시 코드를 반환하도록 보장할 수 있습니다.”

꼬리질문 - 이 둘을 왜 같이 사용하는지에 대해 예시와 함께 설명해주시겠어요? 저는 HashTable을 통해 예를 들어보겠습니다. 해시 테이블은 내부적으로 hashCode() 메서드를 이용하여 객체를 저장하고 검색하는 위치를 결정합니다.
이때 hashCode() 메서드가 반환하는 값이 같다면, 이 두 객체는 같은 버킷에 위치하게 됩니다.
그러나 hashCode가 같다고 이 두 객체가 항상 같은것은 아닙니다. 이는 해시 충돌 떄문인데, 서로 다른 두 객체가 동일한 해시코드를 가질 수 있기 때문입니다.
이런 경우를 구분하기 위해 equals() 메서드를 사용하여 두 객체의 실제 내용을 비교합니다.

따라서 equals() hashCode() 메서드는 항상 함께 오버라이드 해야합니다. 예를들어 equals() 메서드만 오버라이드 하고, hashCode() 메서드를
오버라이드 하지 않는다면, 내용이 같은 두 객체의 해시코드가 다를 수 있어 해시 기반 컬렉션에서 예상치 못한 결과를 가져올 수 있습니다.

제네릭이 무엇이고 어떨때 사용하나요??

제네릭이란 클래스나 메서드에서 사용할 내부 데이터 타입을 미리 지정하는 방식입니다.
이러한 제네릭은 타입 안정성을 보장합니다.즉 제네릭을 사용하면 컴파일 시에 데이터 타입 체크가 가능하므로, 실행 시 불필요한 타입 변환을 방지하고, 잘못된 타입의 객체가 저장되는 것을 막을 수 있습니다.
또한 제네릭을 사용하면 하나의 코드를 다양한 타입에 대해 사용할 수 있기에 코드 재사용성이 좋아집니다.
예를들어 List인터페이스를 구현하는 ArrayList 클래스는 다양한 타입의 객체를 저장할 수 있게끔 제네릭을 사용합니다.(특정 타입에 종속되지 않는 유연한 코드 작성)


참고 - 예를들어 ArrayList에 제네릭을 지정하지 않으면 “hello”도 저장가능하고 1도 저장가능함
이는 Object 형식으로 반환을 하는데 String text = (String)list.get(1)이라고 하면 ClassCastException이 발생할 수 있음
하지만 제네릭을 사용하면 컴파일 시점에 오류가 나기에 list.add(1)은 가능하지 않음

꼬리질문 - 와일드카드가 무엇인지 아시나요??
와일드 카드란 제네릭 타입에 다형성을 적용하고자 할때 사용하는 개념입니다.
이것은 ?를 사용하는데, 이때 extends를 통해 와일드 카드의 상한 제한(T와 그 자손만), super를 통해 와일드 카드의 하한 제한(T와 그 조상들만 가능)을 할 수 있습니다.
이러한 와일드 카드를 이용하면 하나의 참조변수로 다른 타입의 지네릭 타입이 지정된 객체를 다룰 수 있습니다.

참고
ArrayList<? extends Product> list = new ArrayList() or new ArrayList

애너테이션이 무엇인지 설명해주세요.

애너테이션이란 프로그램에게 정보를 제공하는 주석을 뜻합니다.
이러한 애너테이션에는 자바에서 기본적으로 제공하는 표준어노테이션(@Override, @Deprecated)와 다른 어노테이션에 대한 정보를 제공하는 메타 에너테이션이 있으며,
사용자가 직접 정의를 하여 사용할 수 있습니다.
이러한 애너테이션을 사용하여 롬북과 같이 프로그램 작성시 코드를 자동 생성하도록 정보를 제공할수도 있고, 런타임에 특정 기능을 실행하도록 정보를 제공할 수 있습니다.(@Transactional)

JDK, JRE, JVM란 무엇인지 설명해주세요.

JDK(Java Development Kit)이란 Java 언어로 소프트웨어를 개발하기 위한 소프트웨어 개발 도구 모음을 뜻합니다.
이러한 JDK에는 자바 개발시 필요한 라이브러리들과, java compiler(자바 코드 -> 바이트 코드)과 jar(java 클래스 파일을 하나의 아카이브 파일로 패키징)와 같은 개발 도구들이 포함되어있습니다.
그리고 자바 프로그램을 실행시키기 위한 JRE(Java Runtime Environment)또한 포함되어있습니다.(라이센스에 따라 여러 JDK 종류가 있음)
JRE는 Java 응용 프로그램을 실행하는 데 필요한 최소 환경을 뜻하며 여기에는 클래스 라이브러리, JVM(Java Virtual Machine) 및 배포 도구가 포함됩니다.(이러한 소프트웨어 구성 요소를 사용하여 모든 디바이스에서 바이트 코드를 실행합니다.)
JVM은 Java Virtual Machine의 약자로, 자바 바이트 코드를 실행하는 소프트웨어를 의미합니다.
이러한 JVM은 바이트 코드를 해당 OS나 하드웨어 아키텍처에 알맞은 기계어로 해석 또는 실행해주기에, 플랫폼(운영체제)에 관계 없이 독립적으로 프로그램을 실행할 수 있습니다.


참고 - 바이트코드란 특정 하드웨어가 아닌 가상 머신에서 실행되도록 고안된 특별한 형태의 중간 코드
기계어란 컴퓨터의 CPU가 직접 이해할 수 있는 저 수준의 언어(CPU에 따라 각각 인스트럭션 셋이라고 하는 기계어 명령어 세트를 가짐)
java C에 비해 JVM을 거쳐야 하길래 상대적으로 느림

간단히 자바 컴파일 과정 - 자바 소스코드(.java)는 자바 컴파일러에 의해 바이트코드(.class 파일)로 변환됨.
이렇게 생성된 바이트 코드는 JVM 위에서 실행되는 동안, JVM 내부의 인터프리터에 의해 해당 시스템의 기계어로 해석된 후 실행됨
하지만 인터프리터는 모든 코드라인을 읽으면서, 중복된 코드에 대해서도 매번 다시 읽음
이러한 인터프리터 단점을 극복하기 위해 JIT(Just-in-Time) 컴파일러도 함께 사용함(자주 사용되는 코드 캐싱하여 빠름)
자바 개발 도구인 JDK를 이용해 개발된 프로그램은 JRE에 의해 가상의 컴퓨터인 JVM 상에서 구동됩니다.

JVM이 무엇인지 설명해주세요.

JVM은 Java Virtual Machine의 줄임말로, OS와 Java Application 사이를 중재해주는 가상 머신입니다.
JVM은 JVM 내부에서 클래스 파일을 로드(Runtime Data Area) 및 링크하는 ClassLoader, 실제 실행과 번역을 담당하는 Execution Engine, 힙 메모리에서 참조되지 않는 객체를 삭제하는 Garbage Collector, JVM의 메모리 영역인 Runtime Data Area로 구성이 되어있습니다.

바이트 코드는 인터프리터로 직접 실행되거나, JIT 컴파일러를 통해 기계어로 변환된 후 실행 될 수 있다.

Java Application이 JVM에서 실행되는 과정을 설명해주세요.(or JVM 동작 방식)


자바 소스 코드는 먼저 자바 컴파일러(javac)에 의해 바이트 코드(.class 파일)로 변환됩니다.
이러한 바이트 코드가 준비된 이후, 프로그램을 실행하면 JVM이 OS로 부터 필요한 메모리를 할당받고, 이 메모리는 특정 영역으로 나뉘게 됩니다.
클래스 로더는 동적 로딩을 통해 프로그램 실행에 필요한 파일을 런타임 중에 로드하며, 로딩된 클래스 파일은 Runtime Data Area(JVM 메모리)배치됩니다.
Execution Engine은 이러한 바이트 코드를 해석하고 기계어로 변환하여 실행합니다. JVM의 바이트 코드는 기본적으로 인터프리터 방식으로 실행되지만, 효율성을 위해 JIT(Just in Time)컴파일러를 사용하기도 합니다.

JIT 컴파일러는 자주 사용되는 코드를 기계어로 미리 변환하고, 이후에는 변환된 코드를 직접 실행하여 성능을 향상시킵니다.
그리고 프로그램 실행 도중 JVM의 가비지 컬렉터는 더이상 참조되지 않는 객체를 메모리에서 제거하여 메모리 관리를 합니다.


JIT 컴파일러에 대해 자세히 물어볼시
JIT 컴파일러는 프로그램이 실행되는 동안 어떤 코드가 자주 실행되는지, 즉 어떤 부분이 핫스팟인지 분석합니다.
이렇게 분석을 통해 핫스팟을 식별하면, JIT 컴파일러는 해당 코드를 기계어로 컴파일합니다.
이렇게 컴파일된 코드는 메모리 내에 저장되고,이후 같은 코드가 실행될 경우 빠르게 재사용될 수 있습니다.
인터프리터나 JIT 컴파일러에 의해 변환된 코드는 CPU에서 실행됨

클래스 로더에 대해 아시는대로 설명해주세요.

자바 컴파일러를 통해 생성된 클래스 파일들은 각 디렉터리에 흩어져 있습니다.
이렇게 흩어져 있는 각각의 클래스 파일을 찾아서 동적로딩 방식으로 JVM 메모리 영역에 탑재해주는 것이 클래스 로더입니다.
클래스 파일의 로딩 순서는 Loading- Linking - initialization 과정을 거칩니다.
로딩은 클래스 파일(바이트 코드)을 읽어 JVM 메모리에 로드하는 과정이고,Linking은 바이트 코드가 올바른지 검증하고, 정적 변수를 메모리에 할당하는 과정입니다. 마지막으로 Initialization 단계에서는 정적 변수를 초기값으로 설정하게 됩니다. 이 과정까지 끝나면 JVM에서는 클래스 파일을 구동시킬 준비를 마친것입니다.

참고 - 필요하다 할때 로드된다 == 클래스내 특정 메소드가 호출되거나 특정 객체가 생성될때를 의미함

JVM 메모리 구조에 대해 설명해주세요 == 자바 Runtime Area에 대해 설명해주세요.

JVM 메모리 구조는 총 5가지의 영역(메서드 영역, 힙 영역, Stack 영역, Pc Regsiter, Native Method Stack)으로 구성되어있습니다.

method, runtime constant pool, heap are는 모든 스레드에서 공유
stack, pc register, native method stack area는 각 스레드 별로 생성됨

메서드 영역(static 영역이라고도함)은 JVM이 시작될때 생성되며, 모든 스레드가 공유하는 영역입니다.(바이트 코드가 로드되는 곳)
이 영역에서는 (클래스 로더에 의해 로드된) 클래스의 변수의 이름, 타입, 접근 제어자 등과 같은 클래스 관련 메타데이터 저장됩니다.
그외에도 static 변수, 인터페이스 등이 저장됩니다.(jvm이 동작하고 클래스가 로드되어 프로그램이 종료될떄 까지 저장됨)

힙 영역은 런타임에 객체를 동적으로 할당하기 위한 공간으로 new 연산자로 생성된 객체나 배열이 인스턴스가 저장됩니다.
이러한 힙 영역에서 참조되지 않는 객체는, 가비지 컬렉터에 의해 제거됩니다.

(유의할 점은 힙 영역에 생성된 객체와 배열은 Reference Type으로서, JVM 스택영역의 변수나 다른 객체 필드에서 참조된다는 것!!!!)
즉, 힙의 참조 주소는 스택이 가지고 있고, 해당 객체를 통해서만 힙 영역에 있는 인스턴스 핸들링할 수 있다.

Stack 영역은 각 스레드가 작업을 수행하는 과정에서 지역 변수(+매개변수)와 같이 임시적으로 사용되는 변수나 정보들이 저장되는 영역입니다.
특정 메서드가 호출될 때, 해당 메서드와 관련된 정보(지역변수, 반환값, 메서드에 대한 참조 등)는 스택 프레임(현재 실행중인 메서드 상태 정보 저장)이라는 구조로 스택에 push됩니다.
메서드의 수행이 끝나면 해당 스택 프레임은 pop되어 제거됩니다.

PC Register(CPU 레지스터와 다름)는 현재 (스레드가) 수행중인 JVM 명령어 주소를 저장하는 공간입니다.
이때 JVM 명령(Instruction)의 주소는 쓰레드가 어떤 부분을 무슨 명령으로 실행해야 할지에 대한 기록을 가지고 있습니다.
메서드 호출이 발생할 경우 PC Register의 값은 현재 실행 중인 명령어 주소에서 변경되어, 호출된 메서드의 첫번째 명령어 주소로 설정되고, 메서드 실행이 완료되면 원래의 주소로 복귀하게 됩니다.

마지막으로 Native Method Stack은 기계어로(바이트 코드 X) 작성된 프로그램이나, 자바 이외의 언어로(C, C++ 등) 작성된 네이티브 코드를 실행하기 위한 영역입니다.

여기에는 네이티브 메서드의 실행정보(호출 정보 및 지역 변수 등)가 저장됩니다.


참고 - static 변수와 메서드는 무조건 메서드 영역에 포함됨
메서드 영역에는 런타임 상수풀이 위치하고 있는데, 이는 클래스나 인터페이스에 대한 상수, 메서드와 필드에 대한 참조 등을 저장합니다.이 뿐만 아니라 심볼릭 레퍼런스를 다이렉트 레퍼런스로 변환하기 위한 추가 정보를 포함합니다.
힙 영역에 생성된 객체는 JVM 스택 영역의 변수나 다른 객체 필드에 의해 참조되는데, 이렇게 참조되는 동안은 생존하지만, 더이상 참조되지 않으면 GC의 대상이 됩니다.
기본형 타입 변수는 스택영역에 직접 값을 가지는데 비해, 참조 타입의 변수는 힙 영역이나 메서드 영역의 객체 주소를 가짐

독립적 예시
예를 들어, 한 쓰레드가 현재 실행 중인 명령어를 중단하고 다른 쓰레드가 수행되어야 할 경우(Context Switching), 첫번째 쓰레드의 PC 레지스터는 그 쓰레드가 다시 실행될 때 어디서부터 시작해야 하는지를 가리키는 주소를 유지합니다. 이렇게 함으로써 각 쓰레드는 자신의 작업을 독립적으로 수행하고, 다른 쓰레드의 작업에 영향을 주지 않는 것입니다

자바 메모리 상수풀 영역(RunTime Constant Pool)에 대해 아시는대로 설명해주세요

상수풀 영역은 Method 영역안에 위치하며, 클래스나 인터페이스에 대한 상수(static), 메서드와 필드에 대한 참조 정보 및 리터럴(숫자,문자열 등) 저장하는 영역입니다.

(ex: 참조정보? object.myMethod()는 해당 메서드의 실제 메모리 주소를 가리키지 않음. 대신 상수풀에 저장된 myMethod에 대한 참조정보를 통해 해당 메서드를 찾아감)
문자열 리터럴 또한 Heap 영역이 아닌 이 상수풀에 저장되며, 같은 내용의 문자열 리터럴은 한개만 생성됩니다.
예를 들어 여러 곳에서 “Hello”라는 문자열 리터럴을 사용하더라도, “Hello”라는 문자열 리터럴은 상수풀에 한 번만 저장됩니다. 그 이후로는 해당 문자열 리터럴에 대한 참조가 재사용됩니다.


참고 내용
public class Test{
int a = 10;
// a라는 멤버변수는 힙영역에 저장되어있는 Test의 인스턴스 내에 있음(즉 힙영역에 저장되어있음).10이라는 값은 a가 저장되는 메모리 공간에 저장되므로 힙에 저장됨
static int b = 20; // b라는 멤버변수는 Method Area에 저장되며, 20이라는 값은 b라는 값이 저장되어있는 Method Area라는 곳에 저장됨
Person person = new Person(); // person 참조 변수는 Test의 인스턴스 내에 저장되어 있고, Person 객체 자체는 힙 영역에 새롭게 저장됩니다
Static Person staticPerson = new Person();// staticPerson 참조 변수는 메서드 영역에 저장되며, 새 Person 객체는 힙 영역에 저장됩니다.

public void myMethod(){
Person localPerson = new Person(); // localPerson 참조 변수는 스택 영역에 저장되고, 새로운 Person 객체는 힙 영역에 저장됩니다.
String str = “hello” // str 참조 변수는 스택 영역에 저장되며, “hello” 문자열 자체는 상수 풀에 저장됩니다.
System.out.println(localPerson);
}
}
정리하자면 클래스의 멤버변수(인스턴스 변수,non-static 필드)와 그 객체는 힙 영역에 저장됨
클래스 변수(static 필드)는 메서드 영역에 저장됨
메서드 내부에 선언된 지역변수는 스택 영역에 저장됨
객체는 힙 영역에 생성되며, 그 참조 변수는 선언 위치에 따라 힙(멤버변수), 스택(지역변수), 메서드 영역(static 멤버 변수)에 저장됨.
스트링 리터럴은 상수풀에 저장됨

JNI(Java Native Interface)가 무엇인지에 대해 설명해주세요

JNI는 자바 가상머신(JVM)위에서 실행되고 있는 자바코드가 네이티브 응용 프로그램(하드웨어와 운영 체제 플랫폼에 종속된 프로그램들) 그리고 C언어나 어셈블리 같은 다른 언어들로 작성된 라이브러리들을 호출하거나 반대로 호출되는 것을 가능하게 하는 프로그래밍 프레임워크입니다.
이러한 JNI를 사용하여 C나 C++로 작성된 함수를 호출한다면, 이러한 함수의 실행정보는 네이티브 메서드 스택에 저장됩니다.

자바 변수의 종류와 각각이 메모리 상 어디에 할당되는지 설명해주세요.

자바의 변수는 크게 지역 변수, 매개변수, 인스턴스 변수, 클래스 변수로 나뉩니다.
지역변수는 메서드 내부에 선언되어 있는 변수로, 이는 메서드의 호출과 함께 스택 영역에 할당되며, 메서드가 작업을 완료하면 소멸됩니다.
매개변수는 메서드 호출 시 전달되는 인수를 메서드 내부로 전달하는 변수입니다. 이는 지역변수와 마찬가지로 메서드가 호출될때 매개변수가 스택 영역에 할당되며, 이 변수들은 메서드의 호출이 끝나면 스택에서 사라집니다.
인스턴스 변수는 클래스 내에서 static이 아닌 변수를 뜻합니다. 인스턴스 변수는 클래스의 객체를 생성할때 생성되는 변수로 힙 영역에 할당 되며, 각 객체마다 별도의 저장공간을 가지게됩니다.
마지막으로 클래스 변수는 static 키워드를 사용하여 선언되며, 해당 클래스의 모든 인스턴스에서 공유합니다.
이러한 클래스 변수는 메서드 영역에 할당되며, 클래스가 클래스 로더에 의해 로드될때 한번 생성되어 프로그램이 종료될때 까지 유지됩니다.


참고 - 지역변수와 매개변수는 방식 동일
참고 - 각 스레드는 자신만의 스택을 가지며, 이는 스레드가 생성될때마다 생성
멤버변수 - GC에 의해 관리됨
Counter sub = new Counter()라는 코드가 있다면 new Counter()에 의해 객체 자체는 힙영역에 생성되며 이는 생성된 객체의 메모리 주소를 반환
이 반환된 주소는 sub라는 변수에 할당되는데, sub는 이 메모리 주소를 가지고 있으면서 스택 메모리에 할당됩니다.
따라서 sub는 스택영역에 위치하면서, 그 값으로 힙 영역에 위치한 Counter 객체를 참조하게 됨

int a = 10; 이렇게 했을때 a 뿐만아니라 10또한 스택 영역에 저장됨(정확히는 a라는 공간에 10이 저장됨), 즉 값 자체가 변수에 저장되는 특징
int[] a = new int[5]; 이렇게 하든 Custom[] arr = new Custom[6]; 이렇게 하든 배열은 무조건 힙 영역에 저장됨
위와 같이 참조형 배열의 경우에는 처음에 각 요소는 아무런 객체도 참조하고 있지 않기에 null 값을 가지고 있음
참조형 배열안에는 그 객체들의 주소를 가지고 잇다. 참조만을 저장함으로서 메모리를 효율적으로 사용

가비지 컬렉터란 무엇인가요???

가비지 컬렉터는 자바의 JVM 내에 위치한 메모리 관리 도구입니다. 이는 힙 영역에 할당된 객체 중에서 더 이상 참조되지 않는 객체들을 자동으로 탐지하고 회수하여 메모리를 관리하게 됩니다. C언어의 경우에는 개발자가 직접 메모리를 할당(malloc)하고 해제(free)하는 과정을 거칩니다. 이렇게 메모리 관리를 개발자에게 맡기면, 메모리 누수나 해제하지 않은 메모리로 인한 버그의 문제가 발생할 수 있습니다.
반면 자바의 경우에는 가비지 컬렉터가 이러한 메모리 관리를 대신해주어 메모리누수와 같은 문제를 상당 부분 완화시켜주고 프로그래머가 오로지 로직에 집중할 수 있게 해줍니다. 다만 가비지 컬렉션 동작 시 GC작업을 위한 스레드 외 모든 스레드가 종료 되는 Stop-the-world가 발생할 수도 있습니다.(일부 알고리즘, GC특성상 어쩔수 없음)

꼬리질문 - 메모리 누수란 무엇인가요? 가비지 컬렉터가 메모리 관리를 대행해주기에 개발자는 메모리 누수와 같은 문제를 아예 신경쓰지 않아도 되나요?
메모리 누수(Memory Leak)은 프로그램이 필요하지 않은 메모리를 계속해서 점유하고 있는 상태를 말합니다.
이러한 메모리 누수가 발생한다면, 시간이 지남에 따라 사용가능한 메모리가 점점 줄어들어 성능 저하와 함께 프로그램이 중단되는 현상까지 발생할수 있습니다.

예를들어 개체를 계속 생성만 하고 참조를 해제하지 않으면 해당 객체는 메모리 상에 계속 남게되어 메모리 누수가 발생합니다.
이러한 상황이 반복되면, 사용 가능한 메모리 공간이 점차 줄어들게 되는데 이를 메모리 누수라고 합니다.

자바에서는 가비지 컬렉터가 메모리 관리를 대행하므로 메모리 누수 문제를 상당 부분 완화할 수 있습니다. 하지만, 프로그램 내에서 계속 참조되는 객체, 즉 더 이상 필요하지 않지만 여전히 참조되고 있는 객체는 가비지 컬렉터에 의해 회수되지 않습니다. 따라서, 해당 객체는 메모리 누수의 원인이 될 수 있습니다.
결론적으로, 가비지 컬렉터가 있더라도 개발자는 참조 관리와 객체의 생명주기를 적절히 관리해야 하며, 불필요한 참조를 제거하여 메모리 누수를 방지해야 합니다.

꼬리질문 - Stop the world가 발생하는 이유가 뭐죠?
가비지 컬렉션 도중 다른 스레드가 동작하면 메모리 구조가 변경될 수 있어, 이로 인해 안정적인 메모리 회수가 어려울 수도 있습니다.
이러한 상황을 막고 GC가 안전하게 메모리를 회수하기 위해 애플리케이션의 모든 스레드를 일시 중지(Stop the world)시키게 됩니다.

참고 - 예를들어 db연결 커넥션 반납하는 것들이 있을 것
참고 0 메모리 누수 예제 : 스트림 객체를 사용하고 닫지 않은 경우, 디비 커넥션 얻은 후 닫지 않은 경우 참고 - 개발자는 메모리가 언제 해제되는지 알지 못함.

JAVA의 Garbage Collection이 필요한 이유에 대해 설명해주세요

JAVA에서는 개발자가 직접 메모리를 해제하는 방법을 제공하지 않습니다.
이로 인해 시간이 지남에 따라 사용하지 않는 객체들이 메모리에 계속 존재하게 될 수 있습니다.
이런 미사용 객체들이 메모리를 계속 점유하게 되면 결국 메모리 부족 현상(Heap 메모리 부족)이 발생할 위험이 있습니다
Garbage Collection은 이런 문제를 방지하기 위해 더 이상 참조되지 않는 객체를 자동으로 탐지하고 메모리에서 해제하는 역할을 수행합니다.

가비지 컬렉션 대상 -> 참조되지 않는 객체(ex: 메서드가 끝나는 등의 이벤트로 인해 Heap Area 객체의 메모리 주소를 가지고 있던 참조 변수가 삭제된다면? 어디서든 참조 되지 않는 상태가 됨)

mark and sweep과 mark and compact 알고리즘에 대해 설명해주세요(메모리 해제의 기준을 나타내는 알고리즘)

가비지 컬렉션의 기본 알고리즘 중 하나인 mark and sweep 알고리즘은 두 단계로 구성되어있습니다.
mark 단계에서는 root set에서 시작하여 접근가능한 모든 객체를 마킹합니다.(root set은 GC가 참조를 검색하는 출발점으로, Heap 영역을 참조하는 포인터나 변수 등) method area, stack 등등이 있음)
sweep 단계에서는 마킹되지 않은 객체를 메모리에서 해제합니다. 이렇게 해제된 메모리는 새로운 객체의 할당을 위해 사용할 수 있게 됩니다.

해제 과정을 거친 후, 사용하지 않는 메모리 공간(가비지)이 분산되어있으면, 이들 공간이 합쳐지지 않아 큰 객체를 위한 연속적인 메모리 공간을 확보하는 것 어려워집니다. -> 단편화 된것을 의미
이러한 문제를 해결하기 위해 mark and compact 알고리즘이 제안되었습니다. 이때는 mark and sweep과 다르게 compact라는 과정을 거칩니다.
이 compact 단계에서는 사용중인 객체들을 메모리의 한쪽 끝으로 이동시키고, 그로 인해 메모리 공간을 압축합니다. 이를 통해 연속적인 메모리 공간을 효율적으로 확보하게 됩니다.

참고 - compact과정을 거치면 메모리 할당을 보다 효율적으로 할수 있지만 Stop-the-world 시간이 늘어날수있다는점 유의
mark-compact는 참조되지 않는 객체를 명시적으로 제거하는 sweep 과정을 거치지 않지만, compact 과정을 통해 살아있는 객체만 남기게 됨
즉 메모리에서 직접적으로 제거하는 작업이 없어도, 더이상 참조되지 않는 객체는 더이상 접근할 수 없는 메모리 영역에 남게됨.이후 메모리 할당시 이 영역을 덮어쓸것

가비지 컬렉션의 과정(동작 원리)에 대해 설명해주세요 or Minor GC와 Major GC에 대해 물어보는 경우

가비지 컬렉션은 사용되지 않는 메모리를 회수하는 과정으로, 이를 안전하게 진행하기 위해 대상 메모리 영역의 동작을 잠시 중단시키는 것이 필요합니다.
JVM은 가비지 컬렉션을 수행할 때 “stop-the-world” 이벤트를 발생시킵니다. 이 이벤트 동안에는 가비지 컬렉터 쓰레드를 제외한 모든 애플리케이션 쓰레드가 작업을 중지합니다
GC 작업이 완료되면, 애플리케이션의 모든 쓰레드가 다시 동작을 시작합니다. 가비지 컬렉션은 크게 Minor GC와 Major GC로 구분됩니다.


Minor GC는 Young Generation 영역 내에서 발생하는 GC를 의미합니다.
처음에 생성되는 객체는 Young Generation 영역의 일부인 Eden에 할당됩니다.
객체가 계속 생성되면서 Eden 영역이 가득차면 Minor GC가 실행되는데, 이때 Reachable(참조 가능한) 객체를 식별하고(Mark), 이 객체들은 Survivor 영역으로 이동합니다.
Eden 영역에서 unReachable(참조되고 있지 않은) 객체는 메모리에서 제거되고(sweep 과정을 거치고), Survivor 영역으로 이동한 객체들의 age bit 값은 1씩 증가합니다
이 후 다시 Eden 영역이 신규 객체로 가득 찬다면 다시 한 번 Minor GC가 발생하게 되고, 이때 앞선 작업에서 Survivor 영역에 옮겨진 객체도 포함하여 reachable 객체를 식별합니다. 이러한 과정이 계속 반복되게 됩니다.

Major GC는 Old Generation 영역이 가득 차서 발생하는 GC를 말합니다. Minor GC 과정을 거치면서 age bit 값이 임계값을 넘는 객체는 Old Generation 영역으로 이동하게 되는데, 이 과정을 Promotion이라고 합니다.
Promotion이 반복되면서 Old Generation 영역의 공간이 부족해지면 Major GC(Full GC)가 발생하게 됩니다.
이 과정에서는 JVM의 모든 힙 영역(Young generation, Old Generation, 메타데이터(주소 정보) 포함한 JVM 힙영역(자바 8 이후 :Metaspace, 이전 Permanent)을 대상으로 가비지 컬렉션을 진행합니다.
Major GC는 상당한 시간이 소요될 수 있으며, 이는 Stop-the-world 이벤트를 길게 만들어 애플리케이션의 성능에 영향을 줄 수 있습니다.
따라서, 자바 개발자와 연구자들은 Stop-the-world 시간을 최소화하면서 가비지 컬렉션의 효율을 높이는 다양한 알고리즘을 연구하고 개발해왔습니다.


꼬리질문 - 왜 Young 영역보다 Old영역을 크게 잡을까요??
Young 영역보다 Old 영역을 더 크게 잡는 이유는, 대부분의 객체가 Young 영역에서 빠르게 소멸되기 때문입니다
즉, Young 영역에서는 메모리가 빠르게 회전하며, 더 많은 공간이 필요하지 않습니다. 반면에 Old 영역은 오랫동안 살아남은 객체를 저장하므로, 더 많은 공간이 필요합니다
또한, Old 영역이 더 크게 설정되면 Major GC가 발생하는 빈도를 줄일 수 있습니다. Major GC는 힙의 모든 영역을 대상으로 하기 때문에, 처리 시간이 길고 CPU에 부하를 줍니다. 따라서 Old 영역을 충분히 크게 설정하면 이런 부담을 줄일 수 있습니다.

참고 - Survivor 영역이 2개인 이유
객체를 복사하면서 이동시키는 것이 하나의 영역에서 이루어진다면, 그 영역이 꽉찼을 때 추가적인 공간을 찾아야 하기 때문입니다.
age bit란 객체가 얼마나 오래 살아남았는지 표현하는 데이터입니다.
Young 영역은 새롭게 생성한 객체들이 위치하는 영역이고, Old 영역은 young 영역에서 계속 사용되어 살아남은 객체가 복사되는 영역입니다.


Survivor 영역은 두 부분, Survivor 0와 Survivor 1로 나뉘며 이 중 한 영역은 항상 비어 있어야 합니다.
Young 영역은 새롭게 생성된 객체가 할당되는 공간, Old 영역은 Reachable 상태를 유지하여 살아남은 객체가 복사되는 영역
Eden 영역은 새로 생성된 객체가 위치, Survivor 0/ Survivor 1 은 최소 1번의 GC 이상 살아남은 객체가 존재하는 영역

가비지 컬렉터의 종류에 대해 설명해주세요(or 가비지 컬렉션 알고리즘 종류)

우선 Serial GC는 서버의 CPU 코어가 1개일때 사용하기 위해 개발된 가장 단순한 GC입니다. 이는 mark-sweep-compact 알고리즘을 사용합니다.
이는 GC를 싱글스레드로 처리하여 Stop-the-world 시간이 길다는 단점이 있습니다.
Parallel GC는 자바 8의 디폴트 GC로 Serial GC와 기본적인 알고리즘은 같지만 Minor GC나 Major GC 둘다 멀티스레드로 동작합니다.
따라서 Serial GC보다 Stop-the-world 시간이 감소된다는 특징이 있습니다.(이걸 사용해도 Stop-the-world 발생, 그래서 GC와 애플리케이션 동작이 동시에 실행되는 것은 아님. 다만 CMS나 G1은 일시중지 없이(또는 최소화하여) GC작업을 수행하는 기능 제공하므로, 애플리케이션 동작과 GC과정이 동시에 일부 실행될 수 있다.
CMS(Concurrent Mark-Sweep) GC는 Stop-the-world 시간을 최소화하기 위해 설계된 GC로 애플리케이션 쓰레드와 GC 쓰레드가 동시에 실행되는 구간이 있습니다
그러나, 힙 메모리에서의 공간 확보 및 단편화 문제가 발생할 수 있어(Old 영역에서의 연속적 공간 확보가 어려울수), 주기적인 Full GC가 필요할 수 있습니다(이걸로 단편화 해결하는게 아니라, 단편화 때문에 Old 영역에서 메모리 확보가 어려우면 발생한다는거).
G1 GC는 CMS GC를 대체하기 위한 GC로 자바 9 이후부터 디폴트 GC로 지정된 GC로 애플리케이션 쓰레드와 GC 쓰레드가 동시에 실행되는 구간이 있습니다.
기존 GC는 Heap 영역을 Young/Old로 나누어 사용했지만, G1 GC는 Heap영역을 Region이라는 단위로 나누어 관리합니다(체스 같이 분할).
이때 Region에 Eden,survivor,old 등의 역할을 동적으로 부여하게 되며, 가비지가 많은 영역부터 우선적으로 처리한다는 특징이 있습니다.
이 외에도 ZGC및 Shenandoah(쉐난도) GC가 있으며, 이 둘은 최신 가비지 컬렉터로 애플리케이션의 중단 시간을 극소화할려는 것이 특징입니다.

꼬리질문 - 왜 G1 GC는 Stop-the-world 시간이 짧냐??
G1 GC는 메모리를 여러 영역으로 분할하고 각 영역을 독립적으로 관리함으로써 전체 힙을 한 번에 처리하는 대신 일부 영역만을 대상으로 가비지 컬렉션을 수행합니다. 그리고 가비지가 가장 많이 축적된 영역부터 우선적으로 처리함으로써 효율적인 가비지 컬렉션을 수행하며, 이로 인해 Stop-the-world 시간이 짧아집니다.

꼬리질문 - 왜 CMS는 메모리 단편화 문제가 발생하는지 상세히 설명해주세요
CMS 가비지 컬렉터는 Mark and sweep이라는 알고리즘을 사용하며, 이 알고리즘은 메모리에서 사용되지 않는 객체를 찾아내고 해당 메모리 공간을 해제하는 방식입니다.
하지만 이 과정에서 각기 다른 크기와 위치에 있는 객체들이 해제되면서 메모리 공간이 불 연속적으로 바뀌게 됩니다.
이렇게 나눠진 메모리 공간들은 특정 크기의 객체를 저장하기에 충분하지 않을 수 있습니다.
예를들어 여러 개의 작은 메모리 공간이 있지만 그 공간들이 각각 딸떨어져있다면, 이 공간들을 합치지 않고는 저장할 수 없게 됩니다.
이렇게 메모리가 단편화되면 메모리의 활용도가 떨어지고, 결국 충분한 메모리가 있음에도 Out Of Memory를 발생시킬 수 있습니다.
이러한 문제를 해결하기 위해 일부 가비지 컬렉션 알고리즘은 Compaction과정을 포함합니다.
이 과정에서 가비지 컬렉터는 메모리에 남아있는 객체들을 한쪽으로 몰아서 메모리를 연속적으로 만듭니다.
하지만 이 과정은 추가적인 시간이 소요되며, Stop-the-world 시간을 늘릴수있습니다.
요약하자면 CMS는 Compaction 과정을 포함하지 않기에 메모리 단편화 문제에 노출되어 있습니다.

가비지 컬렉터의 작동의 문제를 진단하는 방법과 해결하는 방법은 무엇이 있을까요??

만약 두 Survivor 영역 모두에 데이터가 존재하거나, 두 영역 모두 사용량이 0 이라면 시스템이 정상적인 상황이 아니라고 판단할 수 있습니다.
또한 GC에 대한 로그를 확인하여 문제를 진단 할 수 있습니다.
문제를 해결하는 방법에는 힙 사이즈를 조정하는 방법이 있습니다.
JVM의 힙 사이즈가 너무 작으면, GC가 자주 발생하여 성능이 저하될수있고, 반대로 힙 사이즈가 너무 크면 GC의 시간이 길어져 애플리케이션의 응답 시간이 길어질수 있습니다.
따라서 애플리케이션의 요구사항이나 성능 목표에 따라 힙 사이즈를 적절히 조정하는 것이 중요합니다.
또한 JVM은 여러 가지 종류의 가비지 컬렉터를 제공합니다. 이러한 가비지 컬렉터는 각 상황에 최적화 되어있기에 요구사항에 따라 적절한 가비지 컬렉터를 선택하여 문제를 해결할 수 있을 것 같습니다.

가비지 컬렉터에 의한 시스템 중단 시간을 어떻게 줄일까요??

우선 애플리케이션의 요구사항과 가장 잘 맞는 가비지 컬렉터를 선택함으로서 시스템 중단 시간을 줄일 수 있습니다.
또한 적절한 힙 크기를 선택하는 것 또한 시스템 중단 시간을 줄일 수 있습니다. 예를들어 힙의 크기를 높이면 GC의 빈도는 줄어들겠지만 실행 시간이 길어질수 있습니다.
따라서 애플리케이션의 메모리 사용 패턴을 이해하여 적절한 힙 크기를 선택한다면 시스템 중단 시간을 줄일 수있습니다.
그리고 가비지 컬렉션 작업과 애플리케이션 작업을 병렬로 실행하도록 설정하면 시스템 중단시간을 줄일 수 있습니다.
또한 개발자입장에서는 불필요한 객체 생성을 막고, 불변 객체(final 키워드로 생성, 내부 상태가 변하지 않음, Thread Safe)를 사용하여 시스템 중단 시간을 줄일수 있습니다.
불변 객체를 활용하면 가비지 컬렉터가 스캔해야 되는 객체 수가 줄어서 스캔해야 하는 메모리 영역이 줄어들기 떄문입니다.

참고 - 불변객체 생성 방법 : 클래스를 final로, 모든 클래스 변수을 private final 으로 선언하기

Stream API에 대해 간략히 설명해주세요.

Stream은 자파8에서 추가된 API로, 자바에서의 일련의 데이터 요소인 배열이나 컬렉션 등의 데이터를 처리하기 위한 기술입니다.
작업을 내부 반복(반복문을 메서드 안으로 숨김)으로 처리하기에 불필요한 코딩을 줄일 수 있고, 원본 데이터로부터 데이터를 읽기만 할뿐, 원본 데이터 자체를 변경하지 않습니다.
연산의 구조는 Stream 생성, 중간연산(sort,distinct 등), 최종연산(forEach, Collect 등)순으로 이어집니다.
중간연산은 데이터를 가공하는 과정이고, 최종 연산은 Stream안의 데이터를 모아서 반환하는 역할을 합니다.

참고 - 중간 연산 작업은 바로 실행되는 것이 아니라, 종결 처리 실행이 필요할때 중간 처리가 실행됨
참고 - 스트림은 1회용, 멀티 스레드를 활용해서 병렬로 연산을 수행할 수 있고
Collection은 외부 반복, Stream은 내부 반복.
성능면에서는 내부 반복이 비교족 좋다. 내부반복은 작업을 병렬 처리하면서 최적화된 순서로 처리해주는 반면, 외부 반복은 명시적으로 컬렉션 항목을 하나씩 가져와야 해서 최적화에 불리
즉 Collection에서 병렬성을 이용하려면 직접 synchronized를 통해 관리해야 함

리플렉션이 무엇인가요??

리플렉션은 런타임 시에 클래스, 메서드, 필드 등의 정보를 조회하거나 조작할 수 있는 기술입니다.
이를 통해 런타임에 동적으로 클래스의 객체를 생성하거나, 메서드를 호출하고, 필드 값을 얻거나 설정하는 것이 가능합니다.
특히, 컴파일 시점에 타입 정보가 결정되지 않고 런타임 시에 동적으로 결정해야 하는 경우에 유용하게 사용됩니다. 그러나 런타임에만 오류를 발견할 수 있기 때문에 주의가 필요합니다.«br>

예시 -Class<?> stringClass = Class.forName(“java.lang.String”);
Method[] methods = stringClass.getMethods();

런타임 시점에 다른 클래스를 동적으로 로딩하여 접근할 때 사용
클래스와 멤버 필드, 메서드 등에 관한 정보를 얻어야 할 때 사용

업캐스팅과 다운 캐스팅에 대해 설명해주세요.

업캐스팅은 하위 클래스의 객체가 부모 클래스로 형변환 되는 것을 말합니다. 근데 업캐스팅하게 되면 하위 클래스의 메서드를 사용할 수 없기 때문

blocking과 non-blocking의 차이

두 가지의 차이점은 다른 주체가 작업을 할때 자신이 코드를 실행할 제어권이 있는지 없는지로 판단할 수 있습니다.
Blocking은 자신의 작업을 진행하다가 다른 주체의 작업이 시작되면 제어권을 다른 주체로 넘깁니다. 따라서 자신은 제어권이 없기에 실행할 수 없고, 다른 주체가 실행을 완료하고 제어권을 돌려줄때까지 아무 작업도 할 수 없습니다.
Non-Blocking은 다른 주체의 작업에 관련없이 자신이 제어권을 가지고 있습니다.

자바의 Fork-Join Pool이 무엇인지 설명해주세요.

Fork Join Pool은 자바 7에서 도입된 병렬 처리를 위한 프레임워크로, Task를 분할하여 작은 서브 태스크로 나눈 후, 서브 태스크를 병렬로 처리하고, 그 결과를 다시 취합하는 형태의 스레드 풀입니다.
Fork Join pool 내부에는 큐가 하나 존재하는데, 여기에 각 Task가 저장되고, 각 쓰레드가 Task를 가져가서 처리합니다.
만약 어떤 쓰레드의 큐가 비어있다면 다른 쓰레드의 큐에서 작업을 가져와 처리하게 되며 이를 stealing이라고 합니다.
(최대한 놀고 있는 Thread를 줄이는 방법으로 설계된것)

동기(Sync)와 비동기(Async)의 차이에 대해 설명해주세요.

동기 방식은 한 작업이 완료될 때까지 다음 작업이 기다려야 합니다. 즉, 하나의 작업이 끝나야 다음 작업을 시작할 수 있습니다.
비동기 방식은 하나의 작업이 완료되지 않아도 다음 작업을 시작할 수 있습니다.



서버에 데이터를 요청한 후 응답을 받아야만 그 다음 작업을 수행하는 경우, 이는 동기적으로 작동한다고 볼 수 있습니다.
서버에 데이터를 요청한 후 응답을 기다리는 동안 다른 작업을 계속 수행하는 경우, 이는 비동기적으로 작동한다고 볼 수 있습니다.

동기 방식은 순서대로 실행되므로 코드의 흐름을 이해하기 쉽지만, 특정 작업에서 지연이 발생하면 전체 시스템의 효율이 떨어질 수 있습니다.
반면, 비동기 방식은 여러 작업이 병렬적으로 실행될 수 있어 효율적이지만, 코드의 흐름이나 상태 관리가 복잡해질 수 있습니다.

자바에서 쓰레드를 생성 및 실행하는 방법에 대해 설명해주세요.

자바에서는 쓰레드를 생성하고 실행하기 위해 주로 두 가지 방법을 사용합니다
Thread 클래스를 상속받아 run 메서드를 오버라이드합니다. 이후 해당 클래스의 객체를 생성하고 start() 메서드를 호출하여 쓰레드를 실행합니다
Runnable 인터페이스를 구현하고 run 메서드를 오버라이드합니다. 그리고 해당 구현체를 Thread의 생성자에 전달하고, Thread 객체의 start() 메서드를 호출하여 쓰레드를 실행합니다.

자바에서 멀티스레드 프로그래밍을 하는 방법에 대해 설명해주세요.

자바의 ExecutorService는 쓰레드 관리 및 병렬 처리를 지원해주는 인터페이스입니다.(쓰레드 라이프 사이클 관리)
ExecutorService는 다양한 종류의 쓰레드 풀을 생성할 수 있는 Executors라는 유틸리티 클래스를 제공합니다.
쓰레드풀 생성 이후에는 ExecutorService의 Submit() 메서드를 통해 작업을 제출하면 Future 객체를 통해 비동기 결과를 얻을 수 있습니다.


멀티 쓰레드 프로그래밍 관련 용어

Future : 비동기 연산 결과를 나타내는 객체
Callable : 결과를 반환하는 작업을 정의하는데 사용되는 함수형 인터페이스
CountDownLatch : 여러 쓰레드가 특정 지점까지 도달할때 까지 대기하게 하는 동기화 도구
Semaphore : 동시에 리소스에 접근할 수 있는 쓰레드의 수를 제한하는 동기화 도구
ExecutorService: 쓰레드의 생성, 관리 및 실행을 관리하는 고수준 인터페이스.
Executors: 다양한 유형의 쓰레드 풀을 생성하는 유틸리티 클래스.

자바에서 동시성 문제를 해결하는 방법들에 대해 설명해주세요.

synchronized 키워드는 메서드나 블록에 사용되고, 해당 부분의 동시 접근을 막아 동기화를 수행합니다.
한 시점에 오직 하나의 쓰레드만이 synchronized로 보호된 영역에 접근할 수 있습니다. 이로 인해 비용이 크고 성능에 영향을 줄 수 있습니다.

volatile 키워드가 붙은 변수는 해당 변수의 모든 연산이 메인 메모리에서 직접 이루어집니다. 이는 CPU 캐시 메모리의 일관성 문제를 해결하며, 변수의 가시성을 보장합니다
일반적으로 하나의 쓰레드만이 write 작업을 수행하며, 나머지 스레드는 read 작업만 수행합니다.
그러나, 여러 스레드가 동시에 Main Memory에 접근하게 되면, 쓰레드 간의 동시 접근 문제가 발생할 수 있습니다. 따라서 volatile은 변수의 가시성 문제는 해결할 수 있으나, 동시 접근 문제를 완전히 해결하지는 못합니다.

Atomic 클래스는 CAS(compare and swap)을 이용하여 동시성을 제어하므로, 여러 쓰레드에서 데이터를 write해도 문제가 없습니다.
synchornized 보다 적은 비용으로 동시성을 보장할 수 있는 것이 특징입니다.


꼬리질문 1 - CAS가 뭐죠?
CAS 알고리즘이란 현재 스레드가 존재하는 CPU의 CacheMemory와 MainMemory에 저장된 값을 비교하여, 일치하는 경우 새로운 값으로 교체하고, 일치하지 않을 경우 기존 교체가 실패되고, 이에 대해 계속 재시도하는 방식입니다
CPU가 MainMemory의 자원을 CPU Cache Memory로 가져와 연산을 수행하는 동안 다른 스레드에서 연산이 수행되어 MainMemory의 자원 값이 바뀌었을 경우 기존 연산을 실패처리하고, 새로 바뀐 MainMemory 값으로 재수행하는 방식입니다.


꼬리질문 2 - 가시성 문제와 동시 접근 문제에 대해 설명해주세요.
가시성 문제는 하나의 스레드에서 변수를 변경했을때, 다른 스레드에서 그 변경을 즉시 확인하지 못하는 상황을 의미합니다.
이는 주로 CPU의 캐시 메모리 떄문에 발생합니다. 한쓰레드가 변수를 변경하면 그 변경이 즉시 메인 메모리에 반영되지 않고, 해당 스레드의 로컬 캐시에만 저장될 수 있습니다.
따라서 다른 스레드는 자신의 로컬 캐시에 있는 이전 값으로 작업을 계속사용하게됩니다.
이러한 문제를 해결하기 위해 volatile을 사용합니다. 이를 통해 변수의 변경이 즉시 메인 메모리에 반영되고, 다른 스레드는 그 변수를 읽을때 항상 메인 메모리의 최신값을 읽게 됩니다.

동시 접근 문제는 두개의 이상의 스레드가 동시에 공유 자원 변경하려할때 발생합니다.
예를 들면, 두 스레드가 동시에 은행 계좌의 잔액을 변경하려고 하면, 최종 결과는 예측하기 어려울 수 있습니다. 이 문제를 ‘race condition’이라고도 합니다
synchronized 키워드나 Lock 인터페이스는 이러한 동시 접근 문제를 해결하기 위해 사용됩니다. 이들은 한 번에 하나의 스레드만 공유 자원에 접근하도록 제한하여 동시성 문제를 방지합니다

참고

자바의 메모리 구조는 이럼 -> CPU가 작업을 처리 하기 위해, 필요한 데이터를 RAM에서 읽어 캐시 메모리에 복제함
작업을 처리한 뒤, 변경된 CPU 캐시 메모리 데이터를 RAM에 덮어씌움(RAM 쓰기 작업)
CPU가 여러개일 경우, 각 CPU별 캐시 메모리에 저장된 데이터가 달라 문제가 발생할 수 있다.

메인 메모리(RAM)는 프로그램이 실행될때 필요한 데이터와 명령어 일시 저장. CPU는 램에서 명령어와 데이터 읽어와서 처리. 상대적으로 느리지만 큰 용량
CPU 캐시 메모리는 CPU 내부의 작은 용량의 빠른 메모리, CPU가 자주 접근하는 데이터나 명령어 저장.
CPU는 먼저 캐시 메모리에서 필요한 데이터나 명령어 찾음. 만약 캐시에 필요한 데이터/명령어가 없는 경우(캐시 미스), 메인 메모리에서 해당 정보를 찾아와 캐시에 저장하고 사용.

예시
여러 스레드가 동시에 실행되면서 메인 메모리와 CPU 캐시 사이에서 일관성 문제가 발생할 수 있습니다. 예를 들어, 한 스레드가 CPU 캐시의 데이터를 수정했지만, 이 변경 사항이 즉시 메인 메모리에 반영되지 않는 경우, 다른 스레드가 오래된 메인 메모리의 데이터를 읽게 될 수 있습니다. 이로 인해 데이터의 불일치 문제가 발생합니다.

synchronized 동기화

TOP