1. 클래스로 인스턴스를 만드는 과정
클래스: 객체를 만들기 위한 설계도
인스턴스: 그 클래스 기반으로 실제 힙에 만들어진 객체

| 컴파일 | .java 파일을 컴파일러 컴파일하여 .class 파일로 만든다. |
| 클래스 로딩 | .class 파일을 JVM이 메모리로 가져와, JVM이 읽을 수 있는 바이트 스트림으로 만든다. - 또한, 해당 객체의 메타데이터를 가리키는 Class 객체를 힙에 저장한다. |
| 클래스 링킹 | 검증, 준비, 해석 등. 이때, static이 붙은 클래스 변수에 기본값(원시타입일 시 0 등, 참조 타입일 시 Null)을 넣는다. |
| 클래스 초기화 | static 변수에 값을 넣는 등 초기화한다. |
| 런타임 준비 | 로딩 링킹 초기화를 거친 후 클래스 사용 가능을 확인 |
| 객체 생성 | new를 통한 객체 생성 코드가 수행될 시, Heap에 메모리 할당, 기본값 초기화, 생성자 실행 및 값 초기화, 참조 반환한다. 이때, 스택 프레임에 해당 참조가 올라간다. |
| 객체 사용 | 객체 메서드 호출 등 비즈니스 작업을 한다. |
| 객체 GC 수거 | 참조가 끊긴 객체에 대해 GC가 메모리에서 수거하여 삭제한다. |
2. Class vs Interface vs Abstract vs Sealed
| class | 객체의 설명서 | 기본 | 객체 생성 |
| interface | 객체의 역할 정의 | 필드를 거의 갖지 않음. | 다형성 |
| abstract | 하위 객체들의 공통 기반 | 필드와 구체적인 구현 또한 가능. 클래스 + 추상 메서드 | 스켈레톤 구현 |
| sealed | 상속 범위 제한 기능 | 상속 받을 수 있는 클래스들을 정의 | switch case를 쉽게 쓸 수 있음 |
3. 익명 객체와 람다
이름이 없는 클래스를 즉석에서 만들어 객체를 생성하는 방식.
interface Calculator {
int calculate(int a, int b);
}
Calculator calculator = new Calculator() {
@Override
public int calculate(int a, int b) {
return a + b;
}
};
Calculator calculator = (a, b) -> a + b;
위와 같이 calculate()를 익명 객체에서 람다로 만들 수 있다.
4. 동일성과 동등성
동일성: 두 참조가 같은 객체 하나를 가리키는가
User user1 = new User("지호");
User user2 = user1;
user1 == user2 //true
동등성: 다른 객체라도 논리적으로 같은 값으로 볼 수 있는가?
new Money(1000)
new Money(1000)
money1 == money2 //false
class Money {
private final long amount;
Money(long amount) {
this.amount = amount;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (!(obj instanceof Money other)) {
return false;
}
return amount == other.amount
}
}
equals를 오버라이딩하지 않을 시 동일성만 검사한다.
4-1. equals() 오버라이드 규칙
반사성 [x.equals(x)는 항상 true]
대칭성 [x.equals(y)가 true라고 하면 y.equals(x)도 true]
추이성 [x.equals(y) == true y.equals(z) == true라면, x.equals(z)도 true]
일관성 [반복 호출 시에도 결과가 같아야함]
null은 flase [x.equals(null)은 항상 false]
4-2. hashCode()
해시코드: 객체를 정수 형태의 해시 값으로 변환
equals와의 계약: (x).equals(y)가 true라면, hashCode()도 반드시 같아야 한다.
class Money {
private final long amount;
Money(long amount) {
this.amount = amount;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (!(obj instanceof Money other)) {
return false;
}
return amount == other.amount
}
@Override
public int hashCode() {
return Long.hashCode(amount);
}
}
이런식으로 구현할 수 있다.
record에서는 hashCode()와 equals() 자동으로 구현해준다.
때문에 아래와 같이 조회할 수 있다.
Map<UserId, User> users = new HashMap<>();
record UserId(long value) {
}
users.put(new UserId(1), user);
users.get(new UserId(1));
4-3. equals와 "==" 주의할 점
자바에서 wrapper 타입은 객체의 재사용을 위한 캐싱을 할 때가 있다.
| Integer | O | 기본 -128 ~ 127 |
| Long | O | 기본 -128 ~ 127 |
| Short | O | -128 ~ 127 |
| Byte | O | 모든 값 -128 ~ 127 |
| Character | O | 보통 0 ~ 127 |
| Boolean | O | TRUE, FALSE 두 객체 재사용 |
| Float | X | 일반적인 값 캐시 없음 |
| Double | X | 일반적인 값 캐시 없음 |
| String | O, 하지만 방식이 다름 | String Pool |
위와 같다. Integer, Long 같은 Wrapper는 valueOf()를 통해 캐시된 객체를 돌려줄 수 있다.
때문에, wrapper는 ==을 사용해도된다고 착각할 수 있으나, 숫자가 조금이라도 커진다면, equals를 써야 한다.
5. String 계열
5-1. String
String은 문자열을 표현하는 불변 객체이다.
불변 객체이기 때문에, 아래와 같은 결과가 나온다.
String a = "hello"
String b = a;
a = a + " world";
=> 값은 "hello world"
=> b 값은 "hello"
즉, b는 초기 a가 들어가 있고, 현 a는 업뎃된 값이 들어가 있다.
5-2. StringBuilder와 StringBuffer
String은 불변이므로 반복적인 문자열 수정에는 계속된 메모리에 String을 저장하기 때문에 비효율적일 수 있다.
때문에, StringBuilder의 append()를 통해, 가변 문자열을 고칠 수 있다.
| String | 불변 | 쓰레드 안전(불변이기 때문) | 일반 문자열 |
| StringBuilder | 가변 | X 쓰레드 안전하지 않음 | 일반적인 문자열 조합 |
| StringBuffer | 가변 | O 쓰레드 안전함 | 여러 Thread가 공유하는 문자열 조합 |
6. Double과 BigDecimal
Double은 부동 소수점 방식으로 0.1은 문제가 있는 값이다.
때문에, 문자열로 더 큰 값을 표현할 수 있는 BigDecimal을 써주어야 한다.
BigDecimal price =
new BigDecimal("1000.50");
BigDecimal quantity =
new BigDecimal("3");
BigDecimal total =
price.multiply(quantity);
7. 타입 파라미터 Generic
어떤 값이 들어갈 수 있고, 어떤 행동을 할 수 있는지를 정하는 규칙
자바는 정적 타입 언어이다. 때문에, 컴파일 시점에 타입을 검사한다.
즉, 제네릭의 핵심 목적은 단순한 코드 재사용이 아닌, 런타임에 발생할 타입 오류를 컴파일 타임에 잡는 것이다.
List list = new ArrayList();
list.add("hello");
list.add(100);
String value = (String) list.get(1); => 타입 캐스팅 에러
제네릭이 없을 시 위와 같은 문제가 발생 가능하다.
class ApiResponse<T> {
private final T data;
ApiResponse(T data) {
this.data = data;
}
public T getData() {
return data;
}
}
public static <T> T first(List<T> list) {
return list.get(0);
}
제네릭은 다형성처럼 쓸 수 없다.
List<Dog> animals = new ArrayList<>();
List<Dog> dogs = new ArrayList<>();
List<Animal> animals = dogs;
animals.add(new Cat()); => animals는 List<Dog>인데 문제발생
Dog <= Animal 이라해서
List<Dog> <= List<Animal>은 아니다.
이를 불공변(invariant)이라 한다.
WildCard를 통하
8. Null과 Exception
9. Collection Framework
10. 함수형 프로그래밍과 람다
11. Stream API
12. I/O와 Resource
| input | 프로그램 밖에서 프로그램으로 | ex) 디스크 파일을 힙으로 load |
| output | 프로그램에서 프로그램 밖으로 | ex) heap/stack에서 디스크 파일로 쓰기 |
12-1. java I/O의 2가지 분류(Byte vs Character)
Java는 보통 문자열 데이터와 바이트 데이터(이미지, 동영상)등을 다룬다.
| Byte Stream | - InputStream - OutputStream |
- InputStream - OutputStream - FileInputStream - FileOutputStream - BufferedInputStream - BufferedOutputStream |
데이터를 바이트 그 자체로 다룸 |
| Character Stream | - Reader - Writer |
- Reader - Writer - FileReader - FileWriter - BufferedReader - BufferedWriter |
문자열을 다룬다. UTF-8, EUC-KR등 Byte-Stream에 인코딩, 디코딩을 추가한 구조 |
12-2. 바이트 기반 I/O의 메서드
//바이트 한개씩 읽기
try (InputStream in = new FileInputStream("a.txt")) {
int data;
while ((data = in.read()) != -1) { //파일을 한 바이트씩 읽는다.
System.out.println(data);
}
}
//바이트 여러개씩 읽기
byte[] buffer = new byte[8192];
int len;
while ((len = in.read(buffer)) != -1) { //파일을 여러 바이트씩 읽는다.
...
}
//바이트 쓰기
byte[] data = "hello".getBytes(StandardCharsets.UTF_8);
try (OutputStream out = new FileOutputStream("a.txt")) {
out.write(data);
}
//파일 Copy
try (
InputStream in = new FileInputStream("source.jpg");
OutputStream out = new FileOutputStream("target.jpg")
) {
byte[] buffer = new byte[8192];
int len;
while ((len = in.read(buffer)) != -1) {
out.write(buffer, 0, len); //buffer에 꽉 차지 않음을 대비
}
}
//flush
out.flush() //남은 바이트를 실제 출력 대상으로 다 밀어냄
//close
in.close();
out.close();
혹은 try-with-resources
12-3. 문자열 기반 I/O 메서드
Reader reader = new FileReader("a.txt", StandardCharsets.UTF_8);
//한 문자 읽기
int ch = reader.read();
//여러 문자 읽기
char[] buffer = new char[1024];
int len;
while ((len = reader.read(buffer)) != -1) {
...
}
//문자 쓰기
Writer writer = new FileWriter("a.txt", StandardCharsets.UTF_8);
writer.write("hello");
12-4. InputStreamReader / Writer
InputStream in = new FileInputStream("a.txt");
Reader reader = new InputStreamReader(in, StandardCharsets.UTF_8);
OutputStream out = new FileOutputStream("a.txt");
Writer writer = new OutputStreamWriter(out, StandardCharsets.UTF_8);
12-5. Buffered I/O
12-6. File/Files/Path
13. GC와 Memory
14. Thread와 동시성
| 프로세스 | 실행 중인 프로그램의 독립된 실행 단위. - 독립된 메모리 공간(Heap/Stack/Code/Data) |
| 스레드 | 프로세스 안에서 실제 코드를 실행하는 실행 흐름 - 프로세스 내 일부 공유된 메모리 공간(Heap/Code/Data => 공유 Stack => 개인 ) |
14-1. 공유 메모리로 인한 스레드 간 동시성 문제
Counter counter = new Counter();
Thread A ─┐
├─→ Counter(count)
Thread B ─┘
두개의 쓰레드가 하나의 Heap 메모리 공간에 접근 가능
1. Race Condition 발생
2. 동기화로 해결
- lock, volatile, atomic 등
14-2. Java Thread 기본 구조
Thread thread = new Thread();
class Thread implement Runnable {
final Runnable task
...
}
interface Runnable {
void run();
}
쓰레드 객체는 Runnable을 구현한다.
즉, 쓰레드 객체를 생성할때는 아래와 같이 작업 내용을 넣어줄 수 있다.
Thread thread = new Thread(() -> System.out.println("조금만 기다려라"));
14-3. start() vs run()
Thread thread = new Thread(() -> System.out.println("조금만 기다려라"));
thread.start(); //새로운 실행 흐름을 만들어 시작(쓰레드 생성)
thread.run(); //똑같은 쓰레드에서 그대로 시작
Thread를 통해 새로운 실행 흐름을 만들어주기 위해서는
start()가 필요하다.

위 흐름과 같이 Thread 객체는 그저 JVM의 Thread Native API를 호출하기 위한 객체에 불과하다.
thread.start()를 해주어야 OS 수준의 새로운 쓰레드가 생성된다.
14-4. 자바에서 Thread의 생명주기


| 상태 | 전이 포인트 | 설명 | 전이 이유 코드 | OS Thread 상태와 매핑 |
| NEW | .. | - Thread 객체만 생성된 상태 | new Thread() | .. |
| RUNNABLE | - NEW - BLOCKED |
- thread.start()를 호출 시 새로운 OS 쓰레드가 생성된 상태 - OS 쓰레드가 CPU를 받기 위해 대기중인 상태 |
thread.start() | - NEW - TASK_RUNNING 이는 Linux에서 R로 표현 |
| BLOCKED | - RUNNABLE | - synchronized 락 경쟁 상태. 락 대기중... |
synchronized(lock){} 을 만남 | - S |
| WAITING | - RUNNABLE | - 시간 제한 없이 다른 이벤트를 기다리는 상태 |
object.wait() thread.join() LockSupport.park() |
- S |
| TIMED_WAITING | - RUNNABLE | - 일정 시간동안 기다리는 상태 | Thread.sleep({시간}) object.wait({시간}) thread.join({시간}) |
- S |
| TERMINATED | - RUNNABLE | - 쓰레드의 모든 작업이 끝난 상태. 해당 쓰레드는 다시 start() 할 수 없다. |
run()의 종료 | - Z |
**Linux의 D 상태는 Disk I/O, file system I/O, device-kernel 작업 등을 뜻한다.
14-5. 동시성과 병렬성
| 동시성 | 하나의 코어가 여러 쓰레드를 Context Switching으로 마치 동시에 수행되도록 보이게 함 - 하나의 작업이 오래걸릴 시 모든 작업은 해당 작업이 끝날때를 기다려야하기 때문에 고안됨 |
| 병렬성 | 여러 작업이 실제로 같은 순간에 동시에 실행 |
1코어라도 동시성 문제가 발생할 수 있다.
A: count 읽음
↓
Context Switch
↓
B: count 읽음
B: count 저장
↓
Context Switch
↓
A: count 저장
14-6. 동시성으로 인한 Race Condition과 임계 구역
| Race Condition | 여러 스레드가 공유 데이터에 동시에 접근할 때, 실행 순서에 따라 결과가 달라지는 문제 |
| 임계 구역 | 공유 데이터를 읽거나 수정해서 동시에 실행되면 문제가 생길 수 있는 코드 영역 |
class Counter {
int count = 0;
void increment() {
------------------------------------> 임계 구역
count++;
------------------------------------<
}
int getCount() {
return count;
}
}
위 코드를 두개의 쓰레드가 동시에 접근하여 증가 후 getCount를 한다고 한다.
이때, 1이었던 값이 2가 돼야하는데, 3으로 나올 수 있다. 이를 Race Condition이라 한다.
임계구역의 판단 근거
임계 구역을 판단할 때, 여러 쓰레드가 동시 접근 시 공유 상태의 불변식이 깨질 수 있는 코드 구간을 봐야한다.
| 공유 상태인가? | - 여러 쓰레드의 접근 가능 |
| 최소 하나가 변경하는가? | - 모두 읽기만 한다면, 임계 구역이 아님 |
| 여러 연산 사이 지켜야 할 불변식이 있는가? | - 읽기, 판단, 변경이 논리적으로 하나의 작업. - 판단은 내가 읽은 값이, 변경하는 순간까지 그대로라는 불변식을 |
* 임계 구역의 기본 해결책은 한 쓰레드만 진입이다.
14-7. Synchronized와 Monitor 락
| Monitor Lock | 모든 객체는 Monitor로 사용될 수 있다. 즉, 락 자원이다. |
| Synchronized | Monitor를 이용하여, 락을 거는 키워드 |
class Counter {
private int count = 0;
public void increment() {
synchronized (this) {
count++;
}
}
}
임계 구역을 synchronized로 감쌋다. 이로 this라는 모니터를 획득하지 않는 한 count++을 할 수 없다.
public synchronized void increment() {
count++;
}
public static synchronized void increment() {
...
}
위처럼 써줄 수도 있다.
Monitor 재진입
synchronized void a() {
b();
}
synchronized void b() {
}
monitor는 재진입이 가능하다.
때문에, 위에서 하나의 쓰레드가 a()에 진입했다고, b()에 진입하지 못하는 일은 일어나지 않는다.
14-8. Volatile, JMM, happens-before
14-9. Atomic/CAS/Lock/Semaphore
| Atomic | 단일 값에 대한 원자적 연산 제공 |
| CAS | CAS(기대값, 변경값)으로 기대값과 변경값이 같으면 기대값은 변경값으로 변경. 즉, 버전이 같을 시 변경 가능 |
| Reentrant Lock | 임계구역에 동시에 들어갈 수 있는 스레드 수를 제한 |
| semaphore | Lock과 비슷하지만 동시에 여러 스레드가 들어갈 수 있게 허용 |
14-10. 동시성 컬렉션
14-11. 동시성 장애
14-12. 쓰레드 풀과 Executor
14-13. I/O Bound와 CPU Bound
14-14. Virtual Thread
15. Annotation과 리플렉션, 프록시
'면접 암기장' 카테고리의 다른 글
| [객체 지향] 기본 객체 지향 개념과 AI (0) | 2026.10.25 |
|---|