자바/자바

(자바) 인프런 강의(자바 ) *널널한 개발자

불광동 물주먹 2025. 9. 2. 15:00

part 1, 2 

JVM과 클래스, 객체 생성, 그리고 알아둬야 할 개념들

1. JVM에서 클래스 로딩 → 객체 생성 과정

자바에서 객체는 JVM이 클래스 로더를 통해 클래스를 메모리에 적재한 후에야 생성할 수 있습니다.
흐름은 다음과 같습니다:

  1. 클래스 로딩:
    클래스 로더가 .class 파일을 읽고 JVM 메모리에 올립니다.
    • 메타데이터, 메서드 정보, static 변수 등이 메소드 영역(자바 8 이후는 Metaspace) 에 저장됨.
  2. 링크 & 초기화:
    • 심볼릭 참조 → 실제 메모리 주소로 연결
    • static 변수 초기화
  3. 객체 생성 (new 키워드):
    • 힙(Heap) 메모리에 객체 공간 확보
    • 멤버 변수 기본값 초기화
    • 명시적 초기화 → 생성자 호출 순으로 진행
  4. 생성자 호출:
    • 객체의 초기 상태를 설정
    • super()를 통해 부모 생성자까지 호출됨

📌 예시

 
class Person {
    String name;
    int age;

    Person(String name, int age) { // 생성자
        this.name = name;
        this.age = age;
    }
}

public class Main {
    public static void main(String[] args) {
        Person p = new Person("홍길동", 20); 
        // 1. Person.class 로드 → 2. 힙에 메모리 할당 → 3. 생성자 호출
    }
}

2. 생성자의 올바른 사용

  • 생성자는 일반 함수와 다르다.
    반환 타입이 없고, “객체를 리턴”하는 개념이 아니라 객체를 초기화하는 역할만 담당합니다.
  • 반환값을 돌려주는 게 아니라 이미 생성된 힙 객체의 참조값을 스택에 할당해주는 것에 가깝습니다.
  • 따라서 생성자 안에서 비즈니스 로직, 외부 서비스 호출 등을 억지로 넣는 건 바보짓.
  • 생성자는 오직 "객체를 정상 상태로 만들기"에만 집중!

📌 Bad Example

 
public class User {
    String name;

    public User(String name) {
        this.name = name;
        sendWelcomeEmail(); // ❌ 생성자에서 이런 기능 수행은 지양
    }
}

 

📌 Good Example

public class User {
    String name;

    public User(String name) {
        this.name = name; // 객체 상태 초기화에만 집중
    }
}

3. 클래스를 바라보는 두 가지 관점

  1. 작성자 관점: 어떻게 내부를 설계할까? (캡슐화, 책임 분리, 효율성)
  2. 사용자 관점: 어떻게 쉽게 쓸 수 있을까? (API 설계, 직관적인 메서드, 불필요한 의존성 최소화)

→ OOP를 잘하려면 항상 사용자 관점을 의식해야 합니다.

📌 예시

 
// 작성자가 잘못 설계한 경우
Car c = new Car();
c.setEngine(new Engine());
c.setWheels(new Wheels());

// 사용자 친화적 설계
Car c = new Car();  // 내부적으로 엔진/바퀴 세팅 완료

4. 참조자와 인스턴스

  • 참조자(reference): 스택에 저장된 객체의 주소값
  • 인스턴스(instance): 힙에 실제 생성된 객체
  • 정적 메서드에서는 인스턴스 참조 불가: this가 없기 때문

📌 예시

 
class Sample {
    int x;
    static int y;

    void instanceMethod() {
        System.out.println(this.x); // 가능
    }

    static void staticMethod() {
        // System.out.println(this.x); // ❌ 오류
        System.out.println(y);        // static은 접근 가능
    }
}

5. static + final = 심볼릭 상수

  • static final 은 클래스 로딩 시점에 메소드 영역(Metaspace)에 저장됨
  • 불변, 공용 값 → 객체마다 따로 복사하지 않음
  • GC 대상 아님 (프로그램 종료까지 유지)

📌 예시

 
class Config {
    static final String APP_NAME = "DonationService"; // 심볼릭 상수
}

 


6. 깊은 복사 vs 얕은 복사

  • 얕은 복사 (shallow copy): 객체의 참조만 복사 → 내부 객체 공유
  • 깊은 복사 (deep copy): 내부 객체까지 새로 생성 → 독립적인 객체

📌 예시

 
class Person implements Cloneable {
    String name;
    Address address;

    public Person clone() throws CloneNotSupportedException {
        return (Person) super.clone(); // 얕은 복사
    }
}

📌 깊은 복사 (복사 생성자 활용)

 
class Person {
    String name;
    Address address;

    Person(Person other) {
        this.name = other.name;
        this.address = new Address(other.address); // 깊은 복사
    }
}

7. String과 String Pool

  • String str = "hello";
    → 문자열 리터럴은 String Pool 에 저장  (metaspace에는 심볼릭으로 참조, 실제 문자열은 heap의 String pool에 저장)
  • new String("hello")
    → 힙에 새로운 객체 생성
  • String은 불변(immutable) → 재사용 가능, GC 부담 줄임

 

 

📌 예시

 

String a = "abc";
String b = "abc";
System.out.println(a == b); // true (같은 풀 객체 참조)

String c = new String("abc");
System.out.println(a == c); // false (힙에 따로 생성)

 


8. 심볼릭 참조

  • 클래스 로딩 시, runtime constant pool에 심볼릭 참조 정보 저장
  • "이 값은 힙 어딘가에 있다. 필요하면 연결해라"라는 정보
  • 실제 실행 시점에 JVM이 실제 메모리 주소로 치환(Resolution)