본문 바로가기

TIL

TIL) Enum, Memory Commit & OverCommit

1. Enum

Enum 은 이전에 쓴 글에도 잘 설명되어 있지만, 간단하게 기억을 정리할 겸..

 

Enum 은 자바에서 정의된 java.lang.enum 을 상속해서 만들어진다.

 

enum 상수들의 초기화는 아래와 같은 순서로 이루어진다.

  1. 클래스 로더에 의해 로딩
  2. 클래스 계층구조 연결
  3. 정적 초기화 블록에 의해 초기화

enum 의 바이트코드를 뜯어보면 알겠지만, 정적 (static) 초기화 블록 이 있다.

여기서 enum 상수들의 초기화가 진행된다.

그리고, 생성자가 private 으로 막혀있다.

 

==> 정적으로 한번만 생성되는 싱글톤 오브젝트 즉, 상수이다.

 

 

[enum 의 값이 초기화 되는 경우]

  1. enum 값을 처음 얻을 때 (ex println(MyEnum.TEST))
  2. enum 의 정적(static) 메서드 를 실행할 때. (ex MyTest.valueOf())
    1. enum 안 그냥 메서드 : enum 상수를 객체처럼 사용해서 호출하는 메서드.
    2. enum 안 정적 메서드 : enum 자체가 정적으로 생성되는 상수이기에, 상수(객체) 를 사용하지 않고 뭔가 사용하는 메서드는 정적 메서드로 선언해야 한다.
  3. val myEnum = Class.forName("Enum FQCN")
  4. Class 클래스의 메서드 enumConstants (ex myEnum.enumConstants)

 

[enum 이 초기화 되지 않는 경우 - 위의 3단계 초기화 중 클래스로더의 로딩, 연결 까지만 진행되는 것들]

  1. (Class.forName 로 얻어온 Class 타입이 아닌) MyEnum.javaClass 의 getEnumConstants 를 제외한 메서드들 : 클래스의 메타데이터에만 접근한다.
  2. public static Class<?> forName(String name, boolean initialize,
                                   ClassLoader loader)
    initialize 인자를 false 로 받는 오버로딩 메서드를 사용한 경우.
  3. val myEnum = ClassLoader.getSystemClassLoader().loadClass("myPackage.MyEnum")
    명시적으로 클래스 로딩만 하고 초기화는 진행하지 않는 경우.

 

 

[enum 초기화 순서]

// given
enum class Week {
    Monday("Mon"), Tuesday("Tue"), Wednesday("Wed");

    init {
//        println(Monday.desc) 이건 런타임시 에러
        println("Init")
    }

    private var desc: String? = null

    constructor(desc: String) {
        this.desc = desc
        println(desc)
    }

    companion object {
        private val ABBREV_MAP: MutableMap<String, Week> = HashMap()

        fun getByAbbreviation(desc: String?): Week? {
            return ABBREV_MAP[desc]
        }

        init {
            println(Monday.desc) // 이건 에러 안남.
            println("Static Init 1")
        }

        init {
            println("Static Init 2")
        }
    }
}

// when
val week = Class.forName("package.Week")

// then
Init
Mon
Init
Tue
Init
Wed
Static Init 1
Static Init 2

 

 

  1. enum 이 생성하는 정적 초기화 블럭
    1. 이 안에서 초기화 블럭과 각 값들의 생성자가 불린다.
  2. 사용자 정의 정적 초기화 블럭

 

위 enum 클래스를 보면 init { } 블럭이 두가지가 있다.

정적 companion 안에도 있고, 밖에도 있다.

 

enum 을 초기화하는 순서가 위와 같기 때문에, 사용자 정의 정적 init() 안에서는 이미 생성된 enum 상수들에 접근할 수 있었지만,

이전에 enum 이 생성하는 정적 초기화 블럭 (객체의 생성자 및 정적이 아닌 init 블럭) 에서는 상수객체에 접근할 수 없다.

 

그리고, 사용자 정의 정적 init 블록도 여러개가 있다면 순서대로 실행된다.

 

따라서, enum 초기화에 공통적으로 초기화하고 싶은 일이 있다면, companion object 안의 마지막 init { } 블록 안에 넣도록 하자.

 

 

참고 : enum 로딩방식

https://stackoverflow.com/questions/28296547/execution-order-of-enum-in-java/28296706#28296706

 

Execution order of Enum in java

I got a question about Enum. I have an enum class looks like below public enum FontStyle { NORMAL("This font has normal style."), BOLD("This font has bold style."), ITALIC("This font...

stackoverflow.com

 

 참고 : enum 로딩 순서

https://stackoverflow.com/questions/11419519/enums-static-and-instance-blocks

 

Enums - static and instance blocks

I had learned that in Java the static block gets executed when the class is initialized and instance block get executed before the construction of each instance of the class . I had always seen the

stackoverflow.com

 

참고

https://jojoldu.tistory.com/122

 

 

 

2. Memory Commit & OverCommit

memory commit : 리눅스에서 프로세스는 커널에게 필요한 메모리를 할당받아 사용한다. 하지만 실제로는, 커널은 프로세스에게 실제로 메모리를 할당해주는게 아니라 메모리를 프로세스에게 할당해준 것 처럼 체크 해둔다. 이를 Memory Commit 이라 한다.

 

over commit : 실제 사용할 수 있는 메모리 이상으로 할당할 수 있는 것을 말한다. 위 메모리 커밋과 깊은 관련이 있다.

 

리눅스는 메모리 커밋이라는 것을 이용해서 요구된 메모리를 그대로 할당(Binding)하는 것이 아니라, 실제 사용되는 곳에서 필요한 만큼만 할당한다.

 

실제로, memory commit 된 총량은 리눅스에서 사용 가능한 총 메모리량보다 클 경우가 생긴다.

이를 over commit 되었다고 말하고, 리눅스는 이런 상황에서도 실제 사용하는 메모리가 over 되지 않으면 메모리 부족 에러가 나지 않는다.

 

만약 실제 사용 메모리도 100%를 넘기게 된다면, OOM-killer 를 사용하여 사용하지 않는, 중요도가 떨어지는 등등의 조건의 프로세스를 죽여서 메모리를 확보한다.

 

결국 리눅스는 실제로 필요하지 않지만 순간적으로 큰 메모리를 필요로 하는 작업을 해야하기 때문에 이런 방식을 취한다고 할 수 있다.

 

[예시]

  1. 4G 물리 메모리 사용가능한 리눅스
  2. 3G 프로세스가 돌고 있음
  3. 자식 프로세스를 fork() - 자식 프로세스를 생성하는 fork() 라는 시스템 콜은 부모 프로세스를 그대로 복사한다. 즉 메모리도 3G
    1. 보통 자식 프로세스를 fork() 한 후에는 exec 류 시스템 콜을 실행하게 되는데, 이 때 메모리는 exec() 에 의해 호출된 프로세스에 의해 덮어써지게 되므로 메모리가 부모 프로세스만큼 필요하지 않다.
    2. 그렇기 때문에, 자식 프로세스를 생성하는 순간 에만 필요하고 이후에는 필요없는 메모리 공간이 있다는 것.
  4. 이 상태에서 6G - 4G = 2G 가 over commit 이며, 실제로 사용하지 않을 확률이 높기 때문에 일시적으로 허용하는게 리눅스의 메모리 관리인 것.

 

https://brunch.co.kr/@alden/16

 

vm.overcommit에 대한 짧은 이야기

Linux Kernel Internal | 이번 글에서는 vm.overcommit에 대해서 다뤄볼까 합니다. 막연하게 메모리를 많이 쓸 수 있게 해주는 거  아니야?라고 생각 해왔었는데요, 살펴보니 조금 다른  의미였습니다. 그

brunch.co.kr

https://hakkyoonjung31.github.io/linux/memory-overcommit/

 

메모리 상승과 오버커밋

사내 테스트용 DB서버에서 프로세스에 메모리할당을 하지못하는 부하문제가 발생하여 메모리 할당에 실패한 시간대에 메모리 사용량을 조사하였다$ sar -r -f /var/log/sa/sarXX결과적으로 %commit가 100

hakkyoonjung31.github.io