싱글톤(Singleton)

•  인스턴스를 오직 한 개만 생성할 수 있는 클래스를 싱글톤 이라고 한다.

•  객체 생성 요청이 여러번 발생하더라도 새롭게 메모리를 할당하여 인스턴스를 만드는 것이 아닌,
    기존에 생성되어 있던 인스턴스를 참조한다.

•  사용 예시 : Scheduling 처리 객체 등

•  장점 : 불필요한 메모리 사용을 줄일 수 있다

•  단점 : 이를 사용하는 클라이언트를 테스트하기 어려워 질 수 있다.

    ◦  싱글톤 인스턴스를 가짜(mock) 구현으로 대체할 수 없기 때문.


싱글톤을 만드는 두가지 방법

•  공통점 : 생성자는 private으로, 인스턴스에 접근할 수 있는 유일한 수단 = public static 멤버

1) public static 멤버를 final 필드로 선언

public class Elvis {
		**public** static **final** Elvis INSTANCE = new Elvis();
		private Elvis() { ... }
}

•  private Elvis 생성자는 Elvis.INSTANCE 를 생성할 때(new Elvis) 딱 한 번 호출됨

•  다른 클래스에서 참조할 수 없는 생성자(private) 이므로 Elvis 클래스가 가지는 인스턴스는 Elvis.INSTANCE 단 한 개 뿐임을 보장할 수 있다.

•  예외) 권한이 있는 클라이언트가 AccessibleObject.setAccessible(리플렉션 API)을 사용해 private 생성자를 호출하는 경우

   ◦  리플렉션 : 개발자가 클래스의 구조를 확인하거나, 값/메소드를 호출해야 할 때 사용됨 (런타임 동작 검사 및 수정에 주로 이용된다)

   ◦  setAccessible : 필드나 메서드의 접근제어 지시자에 의한 제어를 변경하는 메서드

Elvis.setAccessible(true); // 이제 외부에서 private Elvis() 접근가능!

•  장점) 해당 클래스가 싱글톤임이 명백하게 드러나며 간결하다.

   ◦  public static 필드가 final 이므로 절대 다른 객체를 받을 수 없기 때문

 

2) 정적 팩토리 메서드를 public static 멤버로 제공

public class Elvis {
		**private** static **final** Elvis INSTANCE = new Elvis();
		private Elvis() { ... }
		**public static Elvis getInstance() { return INSTANCE; }**
}

•  Elvis.getInstance : 항상 같은 객체의 참조를 반환하므로 제2의 Elvis 인스턴스는 생성될 수 없음.

•  장점 1) API를 바꾸지 않고도 싱글톤이 아니게 변경 가능 (스레드별로 다른 인스턴스를 넘겨주는 등)

•  장점 2) 정적 팩토리 → 제네릭 싱글톤 팩토리 로 변경 가능

•  장점 3) 정적 팩토리의 메서드 참조를 공급자(supplier)로 사용 가능

   ◦  Elvis::getInstance 를 Supplier<Elvis>로 사용하는 식


싱글톤 클래스의 직렬화

•  직렬화란? : 자바 시스템 내부에서 사용되는 객체 또는 데이터들을 외부의 자바 시스템에서도 사용할 수 있도록
                       바이트(byte) 형태로 데이터 변환하는 기술

•  역직렬화 : 바이트로 변환된 데이터를 다시 객체로 변환하는 기술 (JVM)

•  모든 객체 필드를 일시적(transient)이라고 선언하고 readResolve 메서드를 제공해야 함

   ◦  안그러면 직렬화된 인스턴스를 역직렬화할 때 마다 새로운 인스턴스가 만들어진다.

       -  예 ) 새로운 인스턴스를 생성하는 것 = 가짜 Elvis를 생성하는 것

   ◦  역직렬화 과정에서 만들어진 인스턴스 대신 기존에 생성된 싱글톤 인스턴스를 반환해주는 역할

public class Elvis implements Serializable {
	private static final Elvis INSTANCE = new Elvis();
	private Elvis() { ...	}
	public static Elvis getInstance() { return INSTANCE; }

	**private Object readResolve() { return INSTANCE;	}**
}

싱글톤을 만드는 세번째 방법 : 원소가 한개인 Enum 클래스 사용하기

public enum Elvis { INSTANCE; }

•  코드가 매우 간결하고! 또 더 쉽게 직렬화가 가능하다.

•  복잡한 직렬화 상황 혹은 Reflection에 의해 제 2의 인스턴스가 생기는 일을 방지한다.

   ◦  enum은 기본적으로 serializable 하기 때문에 serializable interface 를 따로 구현할 필요가 없다. 따라서 역직렬화 시에 runtime 내부에 존재하는 enum class, 즉 동일한 값을 참조한다.

   ◦  enum class는 외부에서 액세스 할 수 있는 생성자 자체가 없으므로 reflection에 면역! (enum class 생성자 = Sole Constructor / 컴파일러에서 사용하는 것으로 사용자가 직접 호출 불가)

   ◦  따라서 대부분의 상황에서 Enum 선언이 싱글톤을 만드는 가장 좋은 방법이다.

       -  단, 만들고자 하는 싱글톤이 Enum 외의 클래스를 상속해야 한다면 이 방법은 사용할 수 없다.


참고자료

 

싱글톤(Singleton) 패턴이란?

이번 글에서는 디자인 패턴의 종류 중 하나인 싱글톤 패턴에 대해 알아보자. 싱글톤 패턴이 무엇인지, 패턴 구현 시 주의할 점은 무엇인지에 대해 알아보는 것만으로도 많은 도움이 될 것이라

tecoble.techcourse.co.kr

 

[Java] Enum의 사용법

안녕하세요. 지난 시간엔 EnumClass가 무엇이고 어떤 장점들이 있는지 알아보았습니다. 2017/06/27 - [Java] - [Java] enum 이란? (enum 개념익히기) 이번 시간은 Enum 사용방법을 더 알아보면서 Enum에 대한 이.

limkydev.tistory.com

 

[Java & Kotlin] enum class가 완벽한 싱글톤이라 불리는 이유

싱글톤 패턴(Singleton Pattern)이란? 싱글톤은 애플리케이션 상 특정 클래스가 최초 한 번만 메모리를 할당하고 그 메모리에 인스턴스를 만들어 사용하는 디자인 패턴을 의미한다. 객체 생성 요청이

dataportal.kr

 

[java] enum 의 serialize 에 대한 이야기

- enum 은 기본적으로 serializable 하다. - enum 의 serialization 은 다른 serialization 과 조금 다르다. enum 의 serialize 결과는 constant 의 이름뿐이다. enum serialize 시 ObjectOutputStream 에서는 'n..

aroundck.tistory.com

 

GC(Garbage Collector) 란 무엇인가?

•  자바는 객체를 생성하는 new 연산자는 있지만 객체를 소멸시키는 연산자는 없다.

   ◦  개발자가 직접 객체를 소멸시킬 수 없다.

•  객체를 소멸시킨다는 것의 의미는, new에 의해 생성된 객체가 차지하고 있던 공간을 JVM에게 돌려주어
     다시 사용할 수 있는 메모리 영역으로 만들어 준다는 것을 의미한다.

•  new로 할당받은 이후 사용하지 않게 된 객체 혹은 배열 메모리를 Garbage 라고 부른다.

•  이러한 Garbage를 정리하는것이 Garbage Collector의 역할이며, 적절한 시점에 자동으로 Garbage를 수집하여
     가용 메모리에 반환한다.
   ◦ 
GC의 스케줄링은 JVM에서 관리한다.

•  객체는 Heap 영역에 저장되고, Stack 영역에 이를 가리키는 주소값이 저장된다.

•  Heap 영역에는 존재하지만 Stack영역에서 해당 객체를 가리키는 주소값이 없는 객체를 ‘참조되지 않는
     (자신을 가리키는 포인터가 없는, unreachable)객체’ 라고 이야기한다.

•  GC는 해당 객체를 더 이상 사용되지 않는 객체라고 간주하여 이를 메모리에서 제거한다.

 

 

GC 의 필요성

•  GC가 없다면?

   ◦  C/C++ 언어처럼 일일이 메모리 해제를 해줘야 한다.

 

•  메모리 해제를 해주지 않는다면?

   ◦  코드가 돌아갈수록 메모리 가용 공간을 하나씩 차지함에 따라, 속도 등의 프로그램 성능이 매우 악화된다.

 

•  개발자가 직접 메모리 해제가 가능한 C++ 언어에서도 GC 기능을 제공한다.

   ◦  메모리 관리에 있어서 프로그래머의 부담을 덜어주기 때문.

         -  메모리 관리를 프로그래머가 직접 할 수 있지만, 부담이 크고 생산성이 떨어진다.

   ◦  new와 delete 만으로는 이룰 수 없는 무언가가 있다 → 메모리 파편화 현상

         -  new와 delete를 반복하면서 heap메모리에 사용하는 메모리와 사용하지 않는 메모리가 듬성듬성 위치하는 현상으로,
            메모리 사용 효율 저하의 원인이 된다.

         -  GC는 힙을 직접 관리하기 때문에 메모리 파편화 현상을 일정부분 해결할 수 있다.

         -  GC가 동작하는 동안 사용하지 않는 메모리를 정리하면서 힙 메모리의 빈부분을 최적화 할 수 있음

 

 

GC의 종류

Serial GC

•  GC를 처리하는 스레드가 1개

•  CPU 코어가 1개만 있을 때 사용하는 방식이다.

•  Mark-Compact collection 알고리즘을 사용해 GC를 실행한다.

 

Parallel GC

•  GC를 처리하는 스레드가 여러개

•  Serial GC보다 빠르게 객체 처리가 가능하다.

•  메모리가 충분하고 코어의 개수가 많을 때 사용하면 좋다.

 

Concurrent Mark Sweep GC(CMS GC)

•  Stop-The-World

   ◦  GC를 실행하기 위해 JVM이 애플리케이션 실행을 멈추는 것

   ◦  stop-the-world가 발생하면 GC를 실행하는 스레드를 제외한 나머지 스레드는 모두 작업을 멈춤

   ◦  GC 작업을 완료한 이후에 중단한 작업을 다시 시작

•  CMS GC는 stop-the-world 시간이 짧다.

• 애플리케이션의 응답 시간이 빨라야 할 때 유용하다.

• 다른 GC 방식보다 메모리와 CPU를 더 많이 사용한다.

• Compaction 단계가 제공되지 않는다.

 

G1 GC

• 각 영역을 Region 영역으로 나눈다.

• GC가 일어날 때, 전체 영역(Eden, Survivor, Old generation)을 탐색하지 않는다.

• 바둑판 각 영역에 객체를 할당하고 GC를 실행한다.

• 해당 영역이 꽉 차면 다른 빈 영역에 객체를 할당하고 GC 실행한다.

• stop-the-world 시간이 짧다.

• Compaction 단계를 제공한다.

 

Z GC

• 확장 가능하고 낮은 지연율(low latency)을 가진 GC

   ◦  정지 시간이 최대 10ms를 초과하지 않는다.

• Heap의 크기가 증가하더라도 정지 시간이 증가하지 않는다.

   ◦  8MB~16TB에 이르는 다양한 범위의 Heap 처리가 가능하다.

• Stop-The-World 정지 시간을 줄이거나 없앰으로써 애플리케이션의 성능 향상에 기여한다.

   ◦  ZGC의 주요 원리는 Load barrierColored Pointer를 함께 사용하는 것

   ◦  이를 통해 Thread가 동작하는 중간에도 객체 재배치 같은 작업을 수행할 수 있다.

• Z GC는 메모리를 Z Pages라고 불리는 영역으로 나눈다.

   ◦  ZPages는 동적 사이즈(G1 GC와 다름)로 2MB의 배수가 동적으로 생성 및 삭제될 수 있다.

 

 

GC의 장단점

• 장점 : 편리성

   ◦  개발자가 동적으로 할당된 메모리 전체를 관리할 필요가 없다.

   ◦  유효하지 않은 포인터에 접근하거나 이미 한번 해제한 메모리를 또 해제하는 등의 버그를 피할 수 있다.

 

• 단점 : 예측 불가능성

   ◦  가비지 컬렉션이 수행되는 정확한 시점을 알 수가 없다.

         -  프로그램이 예측 불가능하게 일시 정지 될 수 있기 때문에, 실시간 시스템에는 적합하지 않다.

   ◦  가비지 컬렉션이 실행될 때는 반드시 애플리케이션을 중지시키는 Stop The World 가 수행되는데,
        이는 성능 저하의 원인이 될 수 있는 Overhead 를 일으킨다.

 

 

GC의 메모리 수거 방식

• 앞에서 정리했듯이, GC는 동적으로 할당한 메모리 영역 중 사용하지 않는 영역을 탐지하여 해제한다.

   ◦  Stack : 정적으로 할당한 메모리 영역

         -  원시 타입의 데이터 값과 함께 할당, Heap영역에 생성된 Object 타입의 데이터 참조 값 할당

   ◦  Heap : 동적으로 할당한 메모리 영역

         -  모든 Object 타입의 데이터가 할당, Heap영역의 Object를 가리키는 참조 변수가 Stack에 할당

 

• Heap에 저장된 객체를 Stack에서 참조하고 있는지 어떻게 확인할까? : Mark and Sweep

   ◦  가비지 컬렉터가 Stack의 모든 변수를 스캔하면서, 각각 객체를 참조하고 있는지 확인 후 마킹한다 (Mark)

   ◦  Reachable Object가 참조하고 있는 객체도 찾아서 마킹한다 (Mark)

   ◦  마킹되지 않은 객체를 Heap에서 제거한다 (Sweep)

→ JVM의 Heap 영역은 GC관점에서 다시 위의 3가지(4가지) 영역으로 구분될 수 있다.

 

Eden

• 새로운 객체가 할당되는 영역

• Eden 영역의 메모리가 전부 사용중인 경우 GC 발생 (Minor GC)

 → Eden 영역에서 Mark and Sweep

 

S0(Survivor 0)

• Eden 영역의 Reachable 객체가 S0 영역으로 옮겨짐

• Eden 영역의 Unreachable 객체는 메모리에서 해제

• S0 영역의 메모리가 전부 사용중인 경우 GC 발생 (Minor GC)

 → S0 영역에서 Mark and Sweep

 

S1(Survivor 1)

• S0 영역에 있던 객체는 S1 영역으로 이동

• 이동한 객체는 Age 값 증가

• Eden 영역의 Reachable 객체가 S1 영역으로 옮겨짐

• Eden 영역의 Unreachable 객체는 메모리에서 해제

• S1 영역의 메모리가 전부 사용중인 경우 GC 발생 (Minor GC)

 → S1 영역에서 Mark and Sweep

 

S0(Survivor 0)

• S1 영역에 있던 객체는 S0 영역으로 이동

• 이동한 객체는 Age 값 증가

• Eden 영역의 Reachable 객체가 S0 영역으로 옮겨짐

• Eden 영역의 Unreachable 객체는 메모리에서 해제

• S0 영역의 메모리가 전부 사용중인 경우 GC 발생 (Minor GC)

 → S0 영역에서 Mark and Sweep

 

위의 과정을 계속 반복한다

 

Old Generation

• Age 값이 특정 값 이상이 되면 Old Generation 영역으로 이동

   ◦  이 과정을 'Promotion' 이라고 한다.

   ◦  Old Generation 영역이 모두 사용중인 경우 GC 발생 (Major GC)

→ Old Generation 영역에서 Mark and Sweep

 

위의 과정 또한 계속 반복된다

 

• 만약 Young(New) Generation 영역과 Old Generation 영역이 모두 사용중인 경우 GC 발생 (Full GC)

→ 전체 영역에서 Mark and Sweep

 

S0 영역과 S1 영역 중 한쪽은 반드시 비어있다.

 

Permanent Generation

• 해당 영역은 GC의 관리영역이 아니다.

• JVM이 애플리케이션에서 사용하는 클래스 및 메소드를 설명하는 데 필요한 메타데이터를 포함하는 영역

• 애플리케이션에서 사용 중인 클래스에 기반하여, 런타임에 JVM에 의해 채워진다.

• Java SE 라이브러리 클래스 및 방법을 여기에 저장할 수 있다.

 

 

참고자료

 

[JAVA] 가비지 컬렉터(Garbage Collector)

객체의 소멸과 가비지 자바는 객체를 생성하는 new 연산자는 있지만 객체를 소멸시키는 연산자는 없음 => 개발자가 마음대로 객체를 소멸시킬 수 없음 객체 소멸이란 new에 의해 생성된 객체 공간

transferhwang.tistory.com

 

들어가기에 앞서

•  왜 JVM을 공부해야 할까?

  ◦  메모리 구조를 알아야 메모리 관리가 잘 되는 코드를 작성할 수 있기 때문

 

•  왜 메모리 관리를 해야 하는가?

  ◦  메모리 관리에 따라 프로그램의 성능은 월등하게 차이날 수 있다.

  ◦  메모리 부족으로 인한 성능저하 현상은 대부분 메모리에 대한 이해없이 코드를 설계한 경우 발생한다.

 

 

JVM의 개념과 특징

•  Java는 특정 운영체제에 종속되지 않도록 JVM이라는 가상머신 위에서 실행되게끔 만들어진 언어

  ◦  운영체제가 바뀌면? Java 코드는 그대로, JVM 만 다른것을 사용하면 OK

  ◦  리눅스 환경에서 만든 자바 파일을 윈도우에서 실행하고 싶다면 윈도우용 JVM을 설치하면 된다.

 

•  JVM(Java Virtual Machine) 자바와 운영체제 사이에서 중재자 역할을 수행하는 가상 머신

  ◦  자바가 운영체제에 구애 받지 않고 프로그램을 실행할 수 있도록 도와준다.

  ◦  자동으로 메모리 관리를 해준다. (GC)

  ◦  다른 하드웨어와 달리 레지스터 기반이 아닌 스택 기반으로 동작한다는 점이 특징

 

 

.java  프로그램이 실행되는 과정

•  자바 컴파일러에 의해 자바 소스 파일이 바이트 코드 형태의 클래스 파일로 변환된다.

•  클래스 로더가 해당 클래스 파일을 읽어들여 JVM 내에 객체를 로드하고 배치한다.

 

 

JVM 구성 요소

•  Class Loader

  ◦  컴파일 결과로 만들어진 .class 바이트코드 파일을 읽어들여 메모리에 배치

  ◦  로딩, 링크, 초기화 세 가지 과정을 거친다.

      -  런타임 시에 동적으로 클래스를 로드한다.

 

•  Execution Engine

  ◦  클래스 로더를 통해 Runtime Data Area에 배치된 바이트 코드들을 명령어 단위로 읽어서 실행한다.

  ◦  초기 JVM은 인터프리터 방식이었기 때문에 속도가 느리다는 단점이 있었지만,
      바이트 코드를 어셈블러 같은 네이티브 코드로 바꿔주는 JIT 컴파일러 방식을 통해 이를 보완했다.

      -  그러나 JIT를 통해 코드를 변환하는데에도 비용이 발생함

      -  때문에 JVM은 모든 코드를 JIT 컴파일러 방식으로 실행하지 않고,
         인터프리터 방식을 사용하다가 일정 기준이 넘어가면 JIT 컴파일러 방식으로 실행한다.

 

•  Garbage Collector

  ◦  힙 메모리 영역에 저장된 객체들 중 더이상 참조되지 않는 객체들을 탐색 및 제거하는 역할을 수행

  ◦  GC 동작하는 정확한 시간은 예측할 수 없다.

 

•  Runtime Data Area

  ◦  JVM 메모리

  ◦  Java 어플리케이션이 실행되는 와중에 할당받은 메모리영역이다.

 

  ◦  Runtime Data Area 의 주요 영역

       Method area (Meta Space)

      •  모든 쓰레드가 공유하는 메모리 영역

      •  클래스, 인터페이스, 메소드, 필드, Static 변수 등의 바이트 코드를 보관

 

       Heap area

      •  모든 쓰레드가 공유하는 메모리 영역

      •  new 키워드로 생성된 객체와 배열이 생성되는 영역

      •  Method area에 로드된 클래스만 생성 가능

      •  Garbage Collector가 작동하는 영역

 

       Stack area (Thread Stack)

      •  Method를 호출할 때 마다 각각의 스택 프레임(그 메서드만을 위한 공간)이 생성된다.

      •  Method의 parameter, 반환값, 지역변수등을 저장하는 용도로 사용된다.

      •  Method의 동작이 끝나면 해당 스택 프레임은 삭제된다.

 

       Code Cache

      •  JIT 컴파일러가 데이터를 저장하는 영역

      •  자주 접근하는 '컴파일된 코드 블록'이 저장된다.

      •  일반적으로 JVM은 바이트 코드를 기계어로 변환하는 작업을 수행하는데,
          이곳에 저장된 코드는 기계어로 이미 변환된 채 캐시되어 있으므로 빠르게 실행할 수 있다.

 

       Shared Library

      •  애플리케이션에서 사용할 공유 라이브러리가 기계어로 변환되어 저장되는 영역

      •  OS에서 프로세스당 한 번씩 로드된다.

 

       PC Register

      •  쓰레드가 시작될 때 생성되는 메모리로, 쓰레드마다 PC Register가 하나씩 존재한다.

      •  쓰레드가 어떤 부분을 무슨 명령으로 실행해야할 지에 대한 정보를 기록하는 부분

      •  현재 수행중인 JVM 명령의 주소를 가진다.

 

       Native method stack

      •  Java 외의 언어(C/C++)로 작성된 네이티브 코드를 위한 메모리 영역

        ◦  Java Native Interface를 이용하여 네이티브 코드 작성 가능

        ◦  네이티브 코드를 사용하는 이유

            -  하나의 Java 파일은 해당 운영체제 환경에 상관없이 똑같은 결과를 반환해야 한다.
               (운영체제에 따라 서로 다른 JVM이 존재하기 때문에 이것이 가능)
            -  단점 : 운영체제의 모든 기능을 JVM이 담지 못한다

                →  리눅스에서만 돌아가는 코드를 허용하면 윈도우즈에서는 안돌아가는 상황 발생

            -  따라서 운영체제의 고유한 일부 기능은 Java 언어만으로 구현이 불가능하다.

 

 

Class Loader의 개념과 동작 원리

•  자바는 동적 로드, 즉 컴파일 타임이 아니라 런타임(바이트 코드를 실행할 때)에 클래스 로드하고 링크하는 특징이 있다.

•  이 동적 로드를 담당하는 부분이 JVM의 클래스 로더

•  로딩, 링크, 초기화 단계로 나누어진다.

  ◦  로딩

      -  자바 바이트 코드(.class)를 메소드 영역에 저장한다.

      -  각 자바 바이트 코드(.class)는 JVM에 의해 메소드 영역에 다음 정보들을 저장한다.

          1) 로드된 클래스를 비롯한 그의 부모 클래스의 정보

          2) 클래스 파일과 Class, Interface, Enum의 관련 여부

          3) 변수나 메소드 등의 정보

      -  로딩이 끝나면, 해당 클래스 타입의 class 객체를 생성해 Heap 영역에 저장한다.

          →  객체이름.class 또는 인스턴스의 getClass() 형태로 호출했을 때 리턴되는 값을 말함

 

  ◦  링크

      -  Verfiy : 읽어 들인 클래스가 자바 언어 명세 및 JVM 명세에 명시된 대로 잘 구성되어 있는지 검사

      -  Prepare : 클래스가 필요로 하는 메모리를 할당하고, 클래스에서 정의된 필드, 메소드, 인터페이스를 나타내는 데이터 구조를 준비

      -  Resolve : Symbolic Memory Reference 를 Method Area에 있는 실제 Reference로 교체

          →  Book book = new Book(); 이라는 코드가 있다고 할 때, book이라는 참조변수가 Heap에 저장된 실제 Book 클래스를
               가리킬 수 있도록 연결하는 과정이 바로 Resolve 과정이다.

 

  ◦  초기화

      -  static 필드들을 설정된 값으로 초기화된다.

      -  static으로 선언된 변수와 메소드에 메모리를 할당하고 초기값을 채우는 과정.

static final String name = "staticName";

 

 

클래스 로더의 동작

•  클래스 로더는 새로운 클래스를 로드해야 할 때, 위와 같은 방식으로 로드를 수행한다

 

부트스트랩 클래스 로더 (Bootstrap Class Loader)

•  JVM 시작 시 가장 최초로 실행되는 클래스 로더

•  자바 자체의 클래스 로더와 최소한의 자바 클래스(java.lang.Object, Class, ClassLoader)만을 로드한다.

확장 클래스 로더 (Extension Class Loader)

•  부트스트랩 클래스 로더를 부모로 갖는 클래스 로더

•  확장 자바 클래스들을 로드한다. java.ext.dirs 환경 변수에 설정된 디렉토리의 클래스 파일을 로드하고,
     이 값이 설정되어 있지 않은 경우 ${JAVA_HOME}/jre/lib/ext 에 있는 클래스 파일을 로드한다.

시스템 클래스 로더 (System Class Loader)

•  자바 프로그램 실행 시 지정한 Classpath에 있는 클래스 파일 혹은 jar 에 속한 클래스들을 로드

•  작성자가 직접 만든 .class 확장자 파일을 로드한다.

 

 

클래스 로더의 동작 과정

•  JVM의 메소드 영역에 클래스가 로드되어 있는지 확인한다. 만일 로드되어 있는 경우 해당 클래스를 사용

•  메소드 영역에 클래스가 로드되어 있지 않을 경우, 시스템 클래스 로더에 클래스 로드를 요청

•  시스템 클래스 로더는 확장 클래스 로더에 요청을 위임

•  확장 클래스 로더는 부트스트랩 클래스 로더에 요청을 위임

•  부트스트랩 클래스 로더는 부트스트랩 Classpath(JDK/JRE/LIB)에 해당 클래스가 있는지 확인.
     클래스가 존재하지 않는 경우 확장 클래스 로더에게 요청을 넘긴다.

•  확장 클래스 로더는 확장 Classpath(JDK/JRE/LIB/EXT)에 해당 클래스가 있는지 확인한다.
     클래스가 존재하지 않을 경우 시스템 클래스 로더에게 요청을 넘긴다.

•  시스템 클래스 로더는 시스템 Classpath에 해당 클래스가 있는지 확인한다.
     클래스가 존재하지 않는 경우 ClassNotFoundException을 발생시킨다.

 

 

동적 클래스 로딩

•  자바의 클래스 로딩은 클래스 참조 시점에 JVM에 코드가 링크되고, 실제 런타임 시점에 로딩되는 동적 로딩을 거친다.

•  런타임에 동적으로 클래스를 로딩한다는 것은, JVM이 미리 모든 클래스에 대한 정보를 메소드 영역에 로딩하지 않는다는 것을 의미

 

•  로드 타임 동적 로딩 (Load-time Dynamic Loading)

public class HelloWorld { 
        public static void main(String[] args) { 
                System.out.println("안녕하세요!"); 
        } 
}

  ◦  JVM이 시작되고 부트스트랩 클래스 로더가 생성된 후에 모든 클래스가 상속받고 있는 Object 클래스를 읽어온다.

  ◦  클래스 로더는 명령 행에서 지정한 HelloWorld 클래스를 로딩하기 위해, HelloWorld.class 파일을 읽는다.

  ◦  HelloWorld 클래스를 로딩하는 과정에서 필요한 클래스인 java.lang.String과 java.lang.System을 로딩한다.

  ◦  이처럼 하나의 클래스를 로딩하는 과정에서 동적으로 다른 클래스를 로딩하는 것 : 로드 타임 동적 로딩

 

•  런타임 동적 로딩 (Run-time Dynamic Loading)

public class RuntimeLoading { 
        public static void main(String[] args) { 
                try { 
                        Class cls = Class.forName(args[0]); 
                        Object obj = cls.newInstance(); 
                        Runnable r = (Runnable) obj; 
                        r.run(); 
                } catch (Exception e) {
                        e.printStackTrace();
                }
        }
}

  ◦  Class.forName() 메소드가 실행되기 전까지는 RuntimeLoading 클래스에서 어떤 클래스를 참조하는지 알 수 없다.

  ◦  따라서 RuntimeLoading 클래스를 로딩할 때는 어떤 클래스도 읽어오지 않고, RuntimeLoading 클래스의 main() 메소드가
       실행되고 Class.forName(args[0]) 를 호출하는 순간에 비로소 args[0] 에 해당하는 클래스를 로딩한다.

  ◦  클래스를 로딩할 때가 아닌, 코드를 실행하는 순간에 클래스를 로딩하는 것 : 런타임 동적 로딩

 

 

클래스가 로딩되지 않는 경우

•  클래스에 접근하지 않을 때

•  클래스의 정적 변수 사용 (final 키워드)

 

 

Java 메모리 할당 방식

public static void main(String[] args) {
	int number = 12;
	Mydata mydata = new Mydata(15);
}

•  JVM은 기본적으로 Stack과 Heap 두가지 저장 공간에 메모리를 할당한다.

•  간단한 변수인 Primitive Type의 변수들은 Stack에 저장이 가능하지만, 복잡한 변수는 Heap 공간에 저장되고
     Stack에는 해당 Heap 공간을 가리키는 변수가 저장된다.

  ◦  JVM은 코드 한 줄 씩 읽으며 객체를 메모리에 할당한다.

  ◦  읽어들인 줄에 저장해야 할 객체가 있을때, 객체가 Primitive Type이면 값 자체가 Stack에 저장된다.

  ◦  그 외는 Stack에는 객체의 주소값만 저장되고, 실제 값은 Heap 영역에 저장된다.

 

 

call by value 와 메모리 할당

•  Java는 call by reference가 존재하지 않는다.

•  call by reference 같은 call by value 만 존재할 뿐

 

•  call by reference 같은 call by value = 주소값을 복사하여 가져가는 call by value

  ◦  실제로 해당 주소를 가리키고 있는 것이 아니다!

  ◦  단지 주소값을 복사해서 가지고 있을 뿐이다.

  ◦  하나의 공간을 여러 객체가 동시에 가리키고 있을 수 없다!

  ◦  단지 한 공간의 주소를 여러 객체가 동시에 가지고 있을 수 있을 뿐이다.

 

•  참조하고 있는 값을 단순히 바꿀수도 있고, 실제로 가리키고 있는 주소의 값을 바꿀수도 있다.

  ◦  개발자가 선택적으로 수행할 수 있음 (신기하네)

•  arg2 = arg1 을 수행해도 이는 run 메서드 내에 존재하는 arg2가 arg1이 가진 주소값을 복사하여 저장하는 것일 뿐,
     원본 a2와는 독립된 변수이기 때문에 원본 a2는 변경되지 않는다.

public static void main(String args[]) {

	class A {
		public int value;
		A(int value) { this.value = value; }

		void run(int x, int y) {
			x.value = 111; // 주소에 저장된 값이 달라짐
			x = y;         // 주소에 저장된 값에는 영향 X
		}
	}

	A a1 = new A(1);
	A a2 = new A(2);
	run(a1,a2);        // a1 = 111, a2 = 2

}
#include<iostream>
using namespace std;

class A {
	public:
		int value;
		A(int value) { this->value = value; }
};

void run(A* arg1, A* arg2) {
	arg1->value = 111;
	*arg2 = *arg1;
};


int main() {
	A* a1 = &A(1);
	A* a2 = &A(2);

	run(a1, a2);

	cout << a1->value;   // 111
	cout << a2->value;   // 111

	system("pause");
	return 0;
}

 

 

참고자료

 

[Java] JVM의 클래스 로더란?

java-study에서 스터디를 진행하고 있습니다. 클래스 로더란? 자바는 동적 로드, 즉 컴파일 타임이 아니라 런타임(바이트 코드를 실행할 때)에 클래스 로드하고 링크하는 특징이 있다. 이 동적 로드

steady-coding.tistory.com

 

[Java] Java는 Call by reference가 없다

Call by Value와 Call By Reference가 뭘까? 프로그래밍을 하다 보면 꼭 알고 넘어가야 하는 개념이 있습니다. 바로 Call By Value, Reference입니다. 어떤 언어를 공부하든 나오는 개념이기도 합니다. Call by va..

deveric.tistory.com