1. Enum
Enum 은 이전에 쓴 글에도 잘 설명되어 있지만, 간단하게 기억을 정리할 겸..
Enum 은 자바에서 정의된 java.lang.enum 을 상속해서 만들어진다.
enum 상수들의 초기화는 아래와 같은 순서로 이루어진다.
- 클래스 로더에 의해 로딩
- 클래스 계층구조 연결
- 정적 초기화 블록에 의해 초기화
enum 의 바이트코드를 뜯어보면 알겠지만, 정적 (static) 초기화 블록 이 있다.
여기서 enum 상수들의 초기화가 진행된다.
그리고, 생성자가 private 으로 막혀있다.
==> 정적으로 한번만 생성되는 싱글톤 오브젝트 즉, 상수이다.
[enum 의 값이 초기화 되는 경우]
- enum 값을 처음 얻을 때 (ex println(MyEnum.TEST))
- enum 의 정적(static) 메서드 를 실행할 때. (ex MyTest.valueOf())
- enum 안 그냥 메서드 : enum 상수를 객체처럼 사용해서 호출하는 메서드.
- enum 안 정적 메서드 : enum 자체가 정적으로 생성되는 상수이기에, 상수(객체) 를 사용하지 않고 뭔가 사용하는 메서드는 정적 메서드로 선언해야 한다.
- val myEnum = Class.forName("Enum FQCN")
- Class 클래스의 메서드 enumConstants (ex myEnum.enumConstants)
[enum 이 초기화 되지 않는 경우 - 위의 3단계 초기화 중 클래스로더의 로딩, 연결 까지만 진행되는 것들]
- (Class.forName 로 얻어온 Class 타입이 아닌) MyEnum.javaClass 의 getEnumConstants 를 제외한 메서드들 : 클래스의 메타데이터에만 접근한다.
-
initialize 인자를 false 로 받는 오버로딩 메서드를 사용한 경우.public static Class<?> forName(String name, boolean initialize, ClassLoader loader)
-
명시적으로 클래스 로딩만 하고 초기화는 진행하지 않는 경우.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
- enum 이 생성하는 정적 초기화 블럭
- 이 안에서 초기화 블럭과 각 값들의 생성자가 불린다.
- 사용자 정의 정적 초기화 블럭
위 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
참고 : enum 로딩 순서
https://stackoverflow.com/questions/11419519/enums-static-and-instance-blocks
참고
https://jojoldu.tistory.com/122
2. Memory Commit & OverCommit
memory commit : 리눅스에서 프로세스는 커널에게 필요한 메모리를 할당받아 사용한다. 하지만 실제로는, 커널은 프로세스에게 실제로 메모리를 할당해주는게 아니라 메모리를 프로세스에게 할당해준 것 처럼 체크 해둔다. 이를 Memory Commit 이라 한다.
over commit : 실제 사용할 수 있는 메모리 이상으로 할당할 수 있는 것을 말한다. 위 메모리 커밋과 깊은 관련이 있다.
리눅스는 메모리 커밋이라는 것을 이용해서 요구된 메모리를 그대로 할당(Binding)하는 것이 아니라, 실제 사용되는 곳에서 필요한 만큼만 할당한다.
실제로, memory commit 된 총량은 리눅스에서 사용 가능한 총 메모리량보다 클 경우가 생긴다.
이를 over commit 되었다고 말하고, 리눅스는 이런 상황에서도 실제 사용하는 메모리가 over 되지 않으면 메모리 부족 에러가 나지 않는다.
만약 실제 사용 메모리도 100%를 넘기게 된다면, OOM-killer 를 사용하여 사용하지 않는, 중요도가 떨어지는 등등의 조건의 프로세스를 죽여서 메모리를 확보한다.
결국 리눅스는 실제로 필요하지 않지만 순간적으로 큰 메모리를 필요로 하는 작업을 해야하기 때문에 이런 방식을 취한다고 할 수 있다.
[예시]
- 4G 물리 메모리 사용가능한 리눅스
- 3G 프로세스가 돌고 있음
- 자식 프로세스를 fork() - 자식 프로세스를 생성하는 fork() 라는 시스템 콜은 부모 프로세스를 그대로 복사한다. 즉 메모리도 3G
- 보통 자식 프로세스를 fork() 한 후에는 exec 류 시스템 콜을 실행하게 되는데, 이 때 메모리는 exec() 에 의해 호출된 프로세스에 의해 덮어써지게 되므로 메모리가 부모 프로세스만큼 필요하지 않다.
- 그렇기 때문에, 자식 프로세스를 생성하는 순간 에만 필요하고 이후에는 필요없는 메모리 공간이 있다는 것.
- 이 상태에서 6G - 4G = 2G 가 over commit 이며, 실제로 사용하지 않을 확률이 높기 때문에 일시적으로 허용하는게 리눅스의 메모리 관리인 것.
https://brunch.co.kr/@alden/16
https://hakkyoonjung31.github.io/linux/memory-overcommit/
'TIL' 카테고리의 다른 글
TIL) Webserver vs WAS, NGNIX vs Apache (0) | 2022.02.22 |
---|---|
TIL) associate 시리즈 (0) | 2022.02.19 |
TIL) @Transactional Propagation, 스프링에서 초기화 코드를 넣기 (0) | 2022.02.17 |
TIL) TINYINT/BIT, Spring Content-type, 코드를 DB 에서 읽어와서 처리할 때 resource (0) | 2022.02.15 |
TIL) @SqlResultSetMapping, Kotlin-Fold, Reduce (0) | 2022.02.05 |