# 메모리 모델과 관리

## 자바 메모리 모델

* 자바 멀티 스레드 환경에서 공유되는 메모리 할당과 동작 방식을 정의하는 모델
* 데이터에 대한 가시성, 접근성 등에 대한 명세를 포함한다.
* 최신 플랫폼에서는 최적화 등을 이유로 명령이 순서대로 실행되는 것을 보장하지 않는다.
* 멀티 프로세서는 메인 메모리와 별개인 캐시 메모리를 소유할 수 있다.
* 여러 스레드가 완벽히 동기화된 상태를 유지하는 것은 비용이 많이 든다.

### 메모리 특성

<figure><img src="https://946524419-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FZqOTWctN1y9hEXEl4sKd%2Fuploads%2FxYOuhRogEaKS37ACxHp3%2Fimage.png?alt=media&#x26;token=00e8a770-1fd2-4d3a-a554-a4ac7fed00c0" alt=""><figcaption><p>Shared Multiprocessor Architecture</p></figcaption></figure>

* 공유 멀티프로세서 아키텍처
  * CPU 코어마다 L1, L2 캐시가 존재한다.
  * CPU 캐시에 존재하는 내용과 RAM에 존재하는 내용이 동기화되지 않는 상태가 존재할 수 있다.

#### 메모리 일관성

* 서로 다른 스레드가 하나의 데이터에 접근할 때 일관적인 상태를 보장하는 속성
* 여러 스레드가 동일 데이터를 읽을 때 일관적이지 않은 값을 읽는 경우 에러 발생

#### 메모리 가시성

* 멀티스레드 환경에서 한 스레드에서 변경한 값을 다른 스레드에서 언제 보게 될지를 정의한 것
* 어떤 스레드에 의해 값이 변경되었을 때 다른 스레드가 가장 최신 값을 읽을 수 있게 하는 속성

#### final field

* final 필드는 별도의 동기화 처리가 필요 없기 때문에 세이프한 불변 객체 구현을 지원하며 JIT 컴파일러는 레지스터에 최종 캐시 값을 유지할 수 있다.
* 스레드 간 레이스 컨디션일 때에도 불변이다.

#### volatile

* 모든 스레드에서 CPU 레지스터가 아닌 메인 메모리로부터 값을 읽고 쓰도록 강제하는 키워드이다.
* 멀티스레드에서 원자성을 보장해주지 않으므로 synchronized, concurrent 패키지의 AtomicXX 등을 사용해야 한다.
* 현대 멀티프로세서 아키텍처는 쓰기 작업을 바로 갱신하지 않고 레지스터에 담아두었다가 한번에 처리한다.

## 메모리 누수

* 필요하지 않은 메모리의 참조를 해제하지 못해 발생하는 누수를 의미한다.
* 메모리에 할당되어 있지만 실행 코드에서 접근할 수 없는 경우에도 발생한다.
* 시스템 성능을 저하시키고 치명적 에러가 발생할 확률이 높다.
* GC를 사용해 메모리가 관리는 되지만 완벽하지 않다.
* 참조, 비참조 유형의 객체가 존재한다.
* 참조중인 객체이지만 사용되지 않는다면 메모리 누수가 발생한 것이다.

<figure><img src="https://946524419-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FZqOTWctN1y9hEXEl4sKd%2Fuploads%2F2YLyeXa4mmARyBbmIaqF%2Fimage.png?alt=media&#x26;token=24c0f4c0-fad8-4cf1-91d9-9b71b269a3bb" alt=""><figcaption></figcaption></figure>

### 발생 원인

* static field 객체가 사용되지 않는 경우 누수 발생 가능
* 사용한 리소스를 해제하지 않아 발생할 수 있으므로, try-with-resourcse 혹은 finally 블록을 통해 리소스 해제하도록 한다.
* equals, hashcode 메서드를 재정의하지 않으면 불필요한 참조가 발생할 수 있다.
* 아우터 클래스를 참조하는 이너 클래스가 있을 때 이너 클래스를 사용하려면 항상 아우터클래스를 참조해야 한다. 이때 아우터 클래스를 사용하지 않는다면 누수 발생 가능
  * 아우터 클래스를 참조하지 않는 경우 이너클래스를 static클래스로 구현하면 바깥 클래스에 대한 참조를 가지지 않아 메모리 누수가 발생하지 않는다.
* ThreadLocal 클래스를 사용해 데이터를 스레드별로 사용할 때 명시적으로 값 제거해주지 않으면 참조가 유지되어 누수 발생 가능
  * ThreadLocal.remove() 메서드 호출해 명시적으로 참조 제거 필요

### 메모리 누수 예방

* VisualVM, YourKit 등과 같은 프로파일러를 활용한 모니터링 또는 \`-verbose:gc\` 옵션 사용으로 GC 모니터링을 통해 예방할 수 있다.
* 참조 객체(java.lang.ref 패키지)를 통해 메모리 누수를 방지할 수 있다.
* 벤치마크를 통해 성능 모니터링을 미리 진행해 예방할 수 있다.
* 코드리뷰도 중요하다.

### 메모리 최적화 방법

* 컬렉션 사용 시 초기 사이즈 지정해 초기화 하거나, CRUD에 대한 시간 복잡도를 확인해 적절한 컬렉션을 사용
* HashMap 사용시 리사이징이 일어나면 해시값을 다시 조정하기 때문에 이러한 일이 발생하지 않도록 주의
* 불변 객체나 캐싱 형태의 구현을 활용해 볼 것
* 박싱/언박싱을 가급적 피할 것
* Stream API 사용시 메모리를 더 사용하거나 성능이 더 느릴 수 있어 주의할 것

## 스레드 상태 및 메모리 확인

### 스레드 덤프

* 스레드 상태 정보를 확인할 수 있다.
* 자바 프로세스의 모든 스레드에 대한 스냅샷을 떠 문제 진단 시 유용하게 사용될 수 있다.
* 일반 텍스트로 작성되어 내용을 파일에 저장한다.
* jstack, JMC, jvisualvm, jcmd, jconsole 등이 있다.

### 힙 덤프

* 특정 순간에 JVM 메모리 상에 있는 모든 객체에 대한 스냅샷을 떠 확인할 수 있다.
* 앱 실행 시 힙 메모리 사용량 분석 가능 
* 메모리 누수, GC 이슈 등을 해결하는 데 활용
* jmap, VisualVM을 사용하거나 `java -XX:+HeapDumpOnOutOfMemoryError <ClassName>` 설정으로 OutOfMemoryError 발생 시 자동으로 힙 덤프를 수행할 수 있다.

### APM & Profiling

* APM
  * 앱의 성능, 가용성 등을 모니터링 또는 관리하는 것
  * 프로파일링보다 한단계 높은 어플리케이션 수준에서 성능을 모니터링, 분석
* 프로파일링
  * 메모리 사용량, 시간복잡도, 특정 명령의 호출 빈도 등을 측정해 분석하는 것
  * 런타임 동안에 어떤 일이 발생하는 지에 대한 정보를 제공
  * APM과 달리 개발자의 코드 레벨에서 성능을 최적화하는 데에 유용
  * Newrelic, VisualVM 등의 툴이 있다
