JAVA

싱글톤 지연로딩

se0nghyun2 2023. 4. 7. 17:03

싱글톤 지연로딩을 이해하면서 공부한 내용들이다.

 

싱글톤이란?

생성 패턴 중 하나로 인스턴스가 하나만 생성됨을 보장하는 패턴이다.

다시 말하면 인스턴스가 필요할 때, 똑같은 인스턴스를 만들지 않고 기존 인스턴스를 공유하는 것이다.

 

인스턴스가 하나됨을 보장은 어떻게 해야할까?

클래스 로딩 시 클래스 초기화는 최초 1회만 실행된다 라는 특성을 활용할 수 있다.

말로 먼저 설명한다면,

클래스의 초기화는 1번 뿐이기에 이 때 인스턴스를 생성을 하며,

인스턴스 필요 시 클래스 변수에 생성한 인스턴스를 할당한다면 인스턴스는 동일한 클래스변수를 바라볼 것이다.

=>유일성 보장

 

클래스 초기화가 뭔데?

클래스, 인스턴스, 객체 간단한 정의

- 클래스
객체를 생성하기 위한 설계도, 틀

- 인스턴스 
클래스를 바탕으로 실체 구현된 실체 = 메모리에 할당되어 실제 사용될 때 인스턴스 라고 부른다.

- 객체
클래스의 인스턴스들을 대표하는 의미

 

클래스와 인스턴스를 비교하여 생각해본다.

<인스턴스>

- 인스턴스는 어떻게 생설될까?

 => 클래스틀을 가지고 NEW연산자를 통하여 만들어진다.

 

- 인스턴스 변수는 무엇인가?

=> 말그대로 실제 구현체가 가지는 변수들이다.

 

<클래스>

- 인스턴스의 바탕인 클래스는 어디서 생성되는걸까?

=> 컴파일 후 클래스가 로드되면서 초기화된다. 

 

 - 특정 클래스는 여러번 로드될까?

=> 아니다. 클래스는 틀이다. 틀은 한 번만 로드 및 초기화 후 실제 사용은 인스턴스화하여 사용하면된다.

 

- 클래스(static) 변수란 무엇인가?

=> 틀이 가지는 변수, 즉 클래스를 바탕으로 만들어진 인스턴스들이 공유할 수 있는 변수이다.

 

따라서, 클래스 최초 로딩 시 그 때 한번만 수행되는 단계이다.(이 때, 한 번만 수행되는 단계에 인스턴스 생성)

= static블록과 static멤버 변수 값을 할당하는 것을 의미

 

 

기존 인스턴스 공유?

static !!

위 static 특징 중 싱글톤을 위한 내용을 가지고 온다면

RuntimeData Area 중 Method Area 영역은 초기 로드에 필요한 정보들 (패키지클래스, 인터페이스 상수, static변수 ,클래스 멤버 변수 등)이 로드 된 후 메모리에 항상 상주하고 있는 영역이다.

 

static에 대한 착각

static이 적힌 클래스 내부 메소드,변수들은 실행 즉시 모두 메모리에 올라가는 걸로 알고 있었다....

하지만 그렇게 되면 언제 사용될지도 모르는 메소드,변수들을 먼저 올라가 있어 메모리 공간에 대한 낭비로 이어진다.

확인해보니, 즉시 메모리에 올라가는 게 아니라 필요할 때 올라가도록 되어있다!!

 

static과 메모리구조

클래스 로더가 .class 파일 탐색 중 static키워드를 보는 순간 객체를 생성되지 않고도 항상 메모리를 할당해야하는 멤버로 보고 Method Area(스태틱 영역)에 메모리를 할당한다.

 

참고1) 쓰레드는 stack을 제외한 Heap영역과 Static영역을 공유한다.

 

 

 

그렇다면 static메모리 영역 내 공유할 인스턴스를 할당해두는 것이다.

그러면 인스턴스가 필요할 시 static메모리 영역에 있는지 없는지 확인 후 없다면 생성, 있으면 공유를 하면 된다.

더하여 모든 쓰레드가 static영역은 공유하기에 해당 클래스 변수를 공유할 수 있다.


위 내용을 바탕으로 싱글톤을 구현해보았다.

 

1. 이른 초기화 ( Eager initialization)

 

로드 시점에 인스턴스 생성하는 방식이다.

class Singleton{
    //클래스 로드 시점에 단 한번의 클래스 초기화 단계에서 인스턴스 생성
   private static Singleton singleton = new Singleton();

   //외부에서 인스턴스 생성 불가 -> 클래스 로드 시점 말곤 인스턴스 생성 불가
   private Singleton(){}
    
    //클래스 메소드를 통한 클래스변수 반환
   public static Singleton getInstance(){
       return singleton;
   }
}

위 방식은 스레드 세이프하기 하나,  원하는 시점에 인스턴스 생성이 아닌 

클래스 로드 시점에 인스턴스가 생성된다.

 

2. Lazy Holder

 

참고) 클래스 로딩 시점

  • 클래스의 인스턴스 생성
  • 클래스의 정적 메소드 호출
  • 클래스의 정적 변수 사용 (final x)
  • 데이터타입이 클래스 타입인 경우 정적 변수 사용(final 유무 관계없이)
class Singleton{
   //외부에서 인스턴스 생성 불가
   private Singleton(){}

   private static class SingletonHolder{
       private static Singleton INSTACNE = new Singleton();
   }
   
    //클래스 메소드를 통한 클래스변수 반환
   public static Singleton getInstance(){
       //이 시점에 SingletonHolder 클래스 로드되면서 초기화
       return SingletonHolder.INSTACNE;
   }
}

내부 클래스도 결국은 클래스 이기때문에 클래스 로드될 때 딱 한번만 초기화 되는 특성을 이용하여 활용한 방식이다.

 

중첩클래스 Holder는 getInstance 메서드가 호출되기 전에는 참조 되지 않으며,

최초로 getInstance() 메서드가 호출(우리가 원하는 시점) 될 때 클래스 로더에 의해 싱글톤 객체를 생성하여 리턴한다.

우리가 알아둬야 할 것은 holder 안에 선언된 instance가 static이기 때문에 클래스 로딩되면서 초기화 시점에 한번만 초기화된다는 점을 활용한 것이다. 또 final을 써서 다시 값이 할당되지 않도록 한다.


참고 

[Design_Pattern] Singleton(싱글톤)의 고도화 (tistory.com)

☕ 클래스는 언제 메모리에 로딩 & 초기화 되는가 ❓ (tistory.com)

[Java] static inner class 는 언제 로드가 될까? 로드와 초기화? — 일단은 내 이야기 (tistory.com)

클래스는 언제 로딩될까 (tistory.com)