총 10분 중 11분
2001
시즌 2개, 그리고 영화
시즌 2: 5화 “아일랜드”
출연: 이나영, 김민준, 김민정, 현빈
장르: 애초에 역경을 딛고 이룩하는 숭고한 사랑이란 없다. 그 역경 자체가 사랑이다.
프로그램 특징: 그 곳에서 살아남는 사랑이 어떤 모습으로 걸어오는지 기다려 보고 싶다.
Programming 이것이 자바다(3판) 9장 익명객체 풀다가 new와 final을 이해하기 위해 JVM 메모리 동작 공부

7. 다음 Chatting 클래스는 컴파일 에러가 발생합니다. 원인을 설명해보세요.

public class Chatting {
	class Chat {
    	void start() {}
        void sendMessage(String message) {}
    }
    
    void startChat(String chatId) {
    	String nickName = null;
        nickName = chatId;
        
        Chat chat = new Chat() {
        	@Override
            public void start() {
            	while(true) {
                	String inputData = "안녕하세요.";
                    String message = "[" + nickName + "]" + inputData;
                    sendMessage(message);
                }
            }
        };
        
        chat.start();
    }
}

닉네임은 메소드의 지역변수이므로 final의 특징을 가진다. 따라서 값을 변경할 수 없어 String nickName = chatId;로 선언해야 한다.

 

💥  나는 왜 계속 String null로 초기화하면 값 넣을 때 new로 해야하는 줄 알았을까 ㅠ

⇒ 요약: String은 기본형처럼 자주 쓰여서, 특별하게 최적화된 참조형, JVM에서 String Pool이라는 영역을 따로 만들어 관리.

참조형을 null로 초기화하고 new없이 값 대입하려고 하면 컴파일 에러나는 것이 맞다. String만 특이한 경우다. 


new를 써야 하는 경우 vs 안 써도 되는 경우

자바에서의 기본 규칙

  • 기본형(primitive type)→ 값 자체를 스택(stack)에 저장함
  • → int, boolean, double …
  • 참조형(reference type)→ 스택에는 참조(주소)만 저장되고,
  • 진짜 객체는 힙(heap)에 저장됨
  • → String, Array, Class, Object, List 등
상황 new 예시 설명
String 리터럴 String s = "hello"; "hello"는 String pool에 자동 등록됨
String 강제로 새로 만들기 String s = new String("hi"); 힙에 따로 객체 만듦 (비효율적)
배열 생성 int[] arr = new int[5]; 크기 지정해야 하므로 반드시 new 필요
클래스 인스턴스 생성 Student s = new Student(); new 없이 객체 없음
List/Map 등 컬렉션 List<String> list = new ArrayList<>(); 인터페이스라서 반드시 구현체 생성해야 함

왜 어떤 경우에는 new가 필요한가?

null로 초기화된 참조 변수는 아무 객체도 가리키지 않음. → 사용 시 NullPointerException 발생함

Student s = null;
s.name = "kim"; // ❌ NPE 발생

하지만 이렇게 하면 OK:

Student s = new Student();
s.name = "kim"; // ✅ 안전하게 사용 가능

 

질문 답변
참조형은 new로 꼭 생성해야 하나요? ❌ String처럼 안 써도 되는 경우 있음
new로 안 만들면 메모리 할당 안 되나요? 참조만 있고 힙 객체 없음 → NPE 위험
언제 new 써야 하나요? 객체를 직접 만들어야 할 때만 써야 함
null 초기화한 참조형에 값 대입 가능해요? ❌ String 제외, new로 객체 없으면 → 대입 불가, NullPointerException

local class 내부 local variable 은 왜 final처럼 값 변경이 되지 않는걸까??

⇒ 요약: 지역 변수는 스택에 저장되고 메서드 실행이 되면 사라진다. 로컬 클래스/익명클래스는 힙에 저장되어 나중에 스레드나 이벤트로 호출 가능해지는 모순 발생. 이를 막기 위해 JVM이 지역 변수의 복사본을 힙에 같이 저장. 복사값이 원본과 달라지면 동기화 문제가 생기니까 변수 변경을 막아버림

void example() {
int count = 10;

	Runnable r = new Runnable() {
	    @Override
	    public void run() {
	        System.out.println(count); // ← 여기를 참조함
	    }
	};

	r.run();

}

여기서 count는 스택에 위치. Runnable r은 힙에 올라가는 익명 클래스

메서드가 끝나면 count는 사라지는데, 익명 클래스 내부에서는 계속 참조하려 함 → 위험

 

자바는 JVM이 지역 변수의 복사본을 내부적으로 만들어 힙에 같이 저장

복사된 값은 힙에 올라간 객체의 일부 필드처럼 존재해서 익명 클래스 내부에서 안전하게 사용 가능하다.

그런데 복사한 값과 원본이 달라지면 동기화 문제가 생기니까 애초에 변수 변경을 못 하게 막아버림


 

💢 익명 클래스를 재사용한다는 말은 뭐지? 익명 클래스는 선언하면 메소드 영역에 $class 이런식으로 올라가서 1회용으로 사용하고 재사용이 안된다고 알고 있는데 힙에 올라가서 재사용 된다는 건 뭔 말일까

용어 의미 예시
클래스(Class) 코드 정의 (메서드, 필드 정보 등) → .class 파일 public class MyClass {...}
객체(Object) 클래스 기반으로 생성된 인스턴스 (실제 데이터 덩어리) new MyClass()

JVM 기준

영역 설명 관리자
메서드 영역 (Method Area) 클래스의 정의 정보(바이트코드)가 올라감 → 클래스 로딩 시 클래스 로더(정적 구조)
힙 영역 (Heap) 실제 객체(인스턴스)가 생성되어 저장됨 GC(런타임 객체)
스택 영역 (Stack) 지역 변수, 참조 변수 등 저장됨  

실행 과정

Runnable r = new Runnable() {
    @Override
    public void run() {
        System.out.println("익명 클래스");
    }
};
  1. 컴파일러가 자동으로 익명클래스 정의 생성 → static area : Outer$1.class → 1회용
  2. new Runnable() 이므로 객체가 힙에 생성 : 익명 클래스의 인스턴스 생성

익명객체의 재사용성을 메모리 관점에서 생각해본 결론

클래스 로딩될 때 클래스 정의는 메소드 영역에 올라가고, 함수 내 변수는 stack에 올라간다.

이때 익명 객체를 이용하면  Outer$1.class 이런 식으로 메서드 영역에 올라가고, 변수(스택)에 객체 주소(힙)를 연결해 한 번은 사용 가능하다. 

메서드가 종료되면 스택에 올라간 변수가 사라져서 익명 객체에 접근할 길이 없어진다.

힙이 참조가 끊긴 객체는 GC(Garbage Collector)가 수거해가면서 생애주기가 끝난다.

 

지역 클래스의 지역 변수가 final처럼 값 변경이 안되는 이유

지역 변수는 스택에, 클래스 객체는 힙에 올라가서 스택에서 힙 주소값을 주어야 힙에 있는 객체가 사용 가능하다. 

근데 지역 변수는 메서드가 끝나면 사라져 버려서 힙 주소를 잃어버리는 dangling이 일어난다. 클래스 객체는 자신의 주소를 아는 변수가 없으니 GC에서 수거해가면서 사라진다. 

이를 막고자 JVM이 지역변수를 객체를 힙에 올릴 때 복사해서 같이 올린다.

복사된 값은 힙에 올라간 객체의 일부 필드처럼 존재해서 익명 클래스 내부에서 안전하게 사용 가능하다.

근데 스택에 있는 원본 변수 값을 바꿔버리면 힙에 올라간 지역변수명과 동기화 문제가 난다. 그래서 로컬 클래스 내부 지역 변수는 값 변경이 안되도록 final 특성을 가진다. 

Programming 이것이 자바다(3판) 9장 익명객체 풀다가 new와 final을 이해하기 위해 JVM 메모리 동작 공부