-
Notifications
You must be signed in to change notification settings - Fork 1
자바 애플리케이션 성능 및 메모리 관리(JVM)
- 강좌 링크: https://www.udemy.com/course/java-application-performance-and-memory-management/learn/lecture/13816100?
- the java 코드를 조작하는 다양한 방법: https://docs.google.com/document/d/11zgALhqn3igwfs4xc9cYdRPlyS84M6ba_6xz0I2M-Ik/edit
-
두 가지 관점에서 생각
- Memory constraints(메모리 제약)
- application speed(속도)
-
java 언어적인 측면이 아니라 JVM 실행 환경적인 측면에 대한 내용이 주를 이룰 것임
- JIT 컴파일러
-
기본적으로 JVM은 바이트코드를 인터프리팅 방식으로 실행
- 바이트코드를 직접 실행하기 때문에 native보다 느림
- 인터프리팅 방식은 컴파일 방식보다 느릴 수 밖에 없음(한줄 한줄 실행하기 때문에 전체를 보며 최적화가 어려움)
-
JVM은 실행 환경을 프로파일링 하면서 [자주 실행되는 | 복잡한 | 시간이 많이 걸리는] 블록을 [bytecode => native code]로 컴파일 하며 최적화를 한다.
-
프로파일링 및 컴파일 과정은 JVM내의 별도의 스레드를 통해 수행된다.
- 따라서, 코드 실행 스레드에는 영향을 주지 않음
-
JIT 컴파일러가 어떻게 동작하느냐에 따라 성능이 달라질 수 있다.
- 어떤 부분을 언제, 어느 수준으로 컴파일 하고 언제 caching할지??
- 컴파일 할 때에도 0(컴파일 안함)~4까지의 수준이 있다.(jvm은 프로파일링을 통해 얻은 데이터를 기반으로 이 수준을 결정)
- 이러한 요소 중에 개발자 수준 커스텀 가능한 요소가 있을까?!
-
Tuning the code cache size
- level 4로 컴파일 되어 케싱이 될 수 있는 요소가 많은 프로그램의 경우 caching되어 있는 code block이 가득 차 있으며 실행중 일 수 있다. 이러면 더는 새로운 caching 후보가 있어도 cache하지 못하는 경우가 발생한다.
- cache의 max size와 지금 사용되고 있는 size를 알 수 있으며, 지금 사용 되고 있는 size가 max에 근접하다면 cache size 증가를 고려해 볼 만 하다.
-
cache size, 컴파일이 어떻게 일어나는지 등은 application 실행 시 특정 flag 전달을 통해 확인할 수 있고 필요 시 외부 모니터링 툴과 연동할 수도 있음(그때그때 필요 시 찾아보면 되는 요소)
-
-
실행 시 여러 flag를 통해 컴파일 옵션을 줄 수 있다. => 튜닝 가능한 지점
-
java -XX:+PrintFlagsFinal : java 실행 시 지정할 수 있는 flag들을 보여준다.
-
성능에 영향을 줄 수 있는 요소(컴파일러 튜닝)
-
컴파일 프로세스를 실행하는 데 사용할 수 있는 스레드 수
- CICompilerCount flag
- jinfo -flag CICompilerCount [pid] 명령어를 통해 CICompilerCount의 기본 값을 알 수 있음
- 기본값은 3개
- [-XXLCICompilerCount=n] flag를 통해 실행되는 application의 컴파일 용 스레드 수를 지정할 수 있음
-
네이티브 컴파일의 임계값(동일한 메소드가 몇번 수행 될 때, 컴파일하는 것으로 판단 할 것인가?)
- [-XX:CompileThreshold=n] flag를 통해 실행되는 application의 임계 값을 지정 가능
- 기본값은 10000
-
- jvm은 바이트코드를 실행하는 표준
- 바이트코드는 기본적으로 인터프리팅 방식으로 실행 => 느림
- 자주 실행되는 코드 블락을 컴파일 방식으로 실행해서 성능 개선을 도모
- 튜님 요소
- 자주 실행되는 기준을(임계 값을) 튜닝
- 컴파일에 사용되는 스레드 수를 튜닝
- code cache를 위한 cache size를 튜닝
- 성능에 가장 영향을 미치는 메모리 요소는 GC
- GC를 이해하기 위해 JVM 메모리 동작 방식을 이해해야 한다.
- 또한, JVM 메모리 동작 방식은 프로그래밍을 통해 얼마든지 커스터마이징 할 수 있는 요소가 많다!
- (stack과 heap의 개념은 복습 측면이 강함)
- call by value(primitive) vs call by reference(object) 복습
- final 키워드 학습
- 참고자료: https://djkeh.github.io/articles/Why-should-final-member-variables-be-conventionally-static-in-Java-kor/
-
기본 개념: final의 의미는 최종적이란 뜻을 가지고 있습니다. final 필드는 초기값이 저장되면 최종적인 값이 되어 프로그램 실행 도중에 수정을 할 수 없습니다.
- final 필드:
- 값 초기화 후 변경 불가
- final 객체:
- 객체 변수에 final로 선언하면 그 변수에 다른 참조 값을 지정할 수 없습니다. 즉 한번 생성된 final 객체는 같은 타입으로 재생성이 불가능합니다. 객체자체는 변경이 불가능하지만 객체 내부 변수는 변경 가능합니다.
- final 클래스:
- 최종상태가 되어 더이상 상속이 불가
- final 클래스여도 필드는 Setter함수를 통하여 변경은 가능합니다.
- final 메서드:
- 메서드에 final을 사용하게되면 상속받은 클래스에서 부모의 final 메서드를 재정의 할 수 없습니다. 자신이 만든 메서드를 변경할 수 없게끔 하고싶을때 사용되며 시스템의 코어부분에서 변경을 원치 않는 메서드에 많이 구현되어 있습니다.
- 메서드 인자 값에 final 사용:
- final 필드와 마찬가지로 인자값에 final을 사용하는 경우 final 인자값의 변경이 불가능합니다.
- final 필드:
-
사용 이유:
- 코드에 의도를 명확하게 하기 위해
- final 키워드를 사용하면 자바 컴파일러가 최적화 할 수 있는 여지가 생김(final 키워드를 쓰면 변경 불가하기 때문에 인라이닝을 할 수 있다)
-
const vs final
- 둘은 정확히 같은 개념? => 답은 아니다!
-
제목 | c++ const |
java final |
---|---|---|
선언과 동시에 초기화? | 초기화 해야 함 | 초기화 안해도 됨 |
객체의 참조의 경우 참조되는 객체의 상태를 변경하는 것이 가능? | 불가 | 가능 |
객체의 참조의 경우 참조되는 객체 자체를 바꾸는 것이 가능? | 가능 | 불가능 |
- 정리
- final변수의 의미는 한번만 참조를 지정할 수 있다는 것
- 즉, final변수가 참조하고 있는 객체의 상태는 언제든 바뀔 수 있다.
- final을 쓰면 컴파일러에 의해 선택적으로 인라인 처리가 될 수 있기 때문에 성능상의 이익을 얻을 수 있다(최적화 요소)
-
성능 보단 java code에서 실수를 많이 하는 요소와 관련한 chapter
-
escaping reference란?
- 결론부터 말하면 캡슐화 된 객체의 규칙을 깨는 행위임
- 즉, 캡슐화 되어 있는 필드에 대한 참조를 특정 메소드에서 리턴을 하는 등의 행위를 통해 그 참조 값이 외부로 직접 노출되는 것을 의미!
- private 필드의 참조를 외부에 직접 노출시키는 경우, 외부에서 private 필드의 참조 대상을 직접 접근 가능함으로 캡슐화를 위반하게 된다.
-
그럼 어떻게 escaping reference를 피할 수 있을까?
-
참조를 바로 노출하지 말고 iterator를 노출
- 근본적으로 escaping reference 해결하지는 않음
- 단지 개발자가 private 참조를 수정하기 좀 더 어렵개 할 수 있음(실수 유발 방지)
- 이 방법은 성능적 문제가 전혀 없음
-
참조를 복사 후 리턴
- iterator방식 보다 더 근본적인 해결책
- 복사 과정에서 약간의 오버헤드
-
참조를 복사 후 리턴(+ immutable한 복사본을 리턴)
- 이 방법도 마찬가지로 참조를 복사한 후 리턴하는 것
- 하지만 복사할 때 immutable한 방식으로 복사하는 것임
- 목적에 따라 리턴한 객체를 변경하고 싶지 않은 경우 이 방법을 사용하면 확실히 컴파일 타임에 오류를 잡을 수 있음
-
인터페이스 사용
- escaping reference 가 발생하는 지점에서 해당 객체의 read only interface를 만든 후 read only 타입으로 리턴 하는 방식
- read only 타입에는 해당 인스턴스를 변경할 수 있는 어떤 메소드도 정의하지 않음
- 이렇게 하면 클라이언트 코드에서도 리턴 타입의 의도가 명시적으로 확인이 됨
- 또한 자연스럽게 escaping reference로 인한 문제상황도 발생하지 않음
- 즉, escaping reference가 발생한 참조 객체에 대해 변경할 수 있는 모든 수단을 막아 둔 interface type으로 리턴을 시키는 것
- 가장 우아한 해결책
- 이 방법에서도 클라이언트 코드에서 구현체로 type casting 하게 되면 escaping reference가 발생 하긴 하나 이렇게 까지 클라이언트가 실수하지는 않을 것임..
-
- 정리
- 캡슐화를 목적으로 선언 되어 있는 필드 중
mutable한 객체의 직접적인 레퍼런스
가 객체 바깥으로 세어 나갈 수 있는 것을 escaping reference라 함 - 주로 mutable한 객체의 레퍼런스를 리턴하는 메소드를 통해 escaping reference가 발생
- immutable한 타입의 경우는 이러한 문제가 발생하지 않는다
- escaping reference 문제의 해결책
- immutable한 객체의 복사본 리턴 or read only interface 정의 후 그걸 리턴
- 캡슐화를 목적으로 선언 되어 있는 필드 중
-
Metaspace란?
- 백기선님 강의의 메소드 영역을 의미하는 듯
-
객체는 항상 힙에 생성될까?
- 우선, 기본적으로는 힙에 생성됨
- 하지만 하나의 메소드에서만 사용될 객체라면 사실, stack에 생성하는 것이 더 효율적일 텐데?(
GC를 안하고 알아서 객체가 없어질 것이니까
)이게 불가능한가? - 기본적으로 객체 생성을 어느 영역에서 할 지에 대한
개발자의 선택권은 java가 제공하지 않음
- 다만, 똑똑한 JVM이 알아서 분석해서 stack에 객체를 할당하는 경우는 있을 수 있음
-
string pool *