메모리 모델과 관리

자바 메모리 모델

  • 자바 멀티 스레드 환경에서 공유되는 메모리 할당과 동작 방식을 정의하는 모델

  • 데이터에 대한 가시성, 접근성 등에 대한 명세를 포함한다.

  • 최신 플랫폼에서는 최적화 등을 이유로 명령이 순서대로 실행되는 것을 보장하지 않는다.

  • 멀티 프로세서는 메인 메모리와 별개인 캐시 메모리를 소유할 수 있다.

  • 여러 스레드가 완벽히 동기화된 상태를 유지하는 것은 비용이 많이 든다.

메모리 특성

  • 공유 멀티프로세서 아키텍처

    • CPU 코어마다 L1, L2 캐시가 존재한다.

    • CPU 캐시에 존재하는 내용과 RAM에 존재하는 내용이 동기화되지 않는 상태가 존재할 수 있다.

메모리 일관성

  • 서로 다른 스레드가 하나의 데이터에 접근할 때 일관적인 상태를 보장하는 속성

  • 여러 스레드가 동일 데이터를 읽을 때 일관적이지 않은 값을 읽는 경우 에러 발생

메모리 가시성

  • 멀티스레드 환경에서 한 스레드에서 변경한 값을 다른 스레드에서 언제 보게 될지를 정의한 것

  • 어떤 스레드에 의해 값이 변경되었을 때 다른 스레드가 가장 최신 값을 읽을 수 있게 하는 속성

final field

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

  • 스레드 간 레이스 컨디션일 때에도 불변이다.

volatile

  • 모든 스레드에서 CPU 레지스터가 아닌 메인 메모리로부터 값을 읽고 쓰도록 강제하는 키워드이다.

  • 멀티스레드에서 원자성을 보장해주지 않으므로 synchronized, concurrent 패키지의 AtomicXX 등을 사용해야 한다.

  • 현대 멀티프로세서 아키텍처는 쓰기 작업을 바로 갱신하지 않고 레지스터에 담아두었다가 한번에 처리한다.

메모리 누수

  • 필요하지 않은 메모리의 참조를 해제하지 못해 발생하는 누수를 의미한다.

  • 메모리에 할당되어 있지만 실행 코드에서 접근할 수 없는 경우에도 발생한다.

  • 시스템 성능을 저하시키고 치명적 에러가 발생할 확률이 높다.

  • GC를 사용해 메모리가 관리는 되지만 완벽하지 않다.

  • 참조, 비참조 유형의 객체가 존재한다.

  • 참조중인 객체이지만 사용되지 않는다면 메모리 누수가 발생한 것이다.

발생 원인

  • 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 등의 툴이 있다

Last updated