Java 최적화 - Chapter12

이유진·2024년 3월 24일

동시 성능 기법

과거에는 HW가 좋아지면 application 성능도 좋아지는 "공짜 점심"을 누리던 시절이 있었다.
현재는 멀티코어 프로세서가 일반화 되어 있다.

JVM의 경우, JIT 컴파일 처럼 VM 스레드들(GC, JIT, 클래스 로딩, 모니터링 및 관리 등)이 멀티코어 환경에서 동시에 실행될 수 있다.

병렬성

암달의 법칙: T(N) = S + (1/N) * (T - s)
S: 순차 실행 파트
T: 총 태스크 소요 시간
N: 프로세서 개수
T(n): 프로세서 개수별 총 태스크 소요시간

프로세서 개수(N)을 무한히 증가시켜도, 총 소요시간은 순차 실행 시간보다 줄어들 수 없다.

만약 데이터 공유 없이 워크로드를 분산시킨다면 이론적으로 속도는 무한히 높일 수 있다.
하지만 어쩔 수 없이 일부 태스크를 순차처리 하게되거나 스레드 끼리 상태나 데이터를 공유한다면 통신 오버헤드가 발생한다.

java 동시성

만약 다른 스레드에서 같은 값을 변경한다면 원하는 결과가 나오지 않을 수 있다.

JMM

java에는 1.0 버전부터 공식적으로 JMM이란 메모리 모델이 존재했다
JMM은 다음과 같은 질문에 답을 찾는 모델이다

  • 두 코어가 같은 데이터를 액세스 하면 어떻게 되는가?
  • 언제 두 코어가 같은 데이터를 바라본다고 장담할 수 있는가?
  • 메모리 캐시는 위 두 질문의 답에 어떤 영향을 미치는가?

JMM은 다음과 같은 내용을 약속한다

  • 순서에 관한 보장
  • 여러 스레드에 대한 업데이트 가시성 보장

JMM은 메모리의 일관성과 가시성을 어떻게 관리하느냐에 따라 두 가지 다른 방식을 고려한다.
1. 강한 메모리 모델: 전체 코어가 항상 같은 값을 바라본다

  • 한 스레드에서 수행한 작업이 다른 모든 스레드에게 즉각적으로 반영되어야 한다
  • 성능에 부담을 줄 수 있다
  1. 약한 메모리 모델: 코어마다 다른 값을 바라볼 수 있고, 그 시점을 제어하는 특별한 캐시 규칙이 있다
  • 한 스레드의 변경 사항이 다른 스레드에 즉시 반영되지 않을 수 있다
  • 특정 조건에서만 변경 사항을 다른 스레드에게 가시적으로 만들어주는 역할을 한다

➡️ JMM은 아주 약한 메모리 모델이다

약한 메모리 모델

  • 약한 메모리 모델일수록 이식성이 좋다
  • 만약 JVM이 강한 메모리 모델로 설계됐다면, native 수준에서 강한 메모리 모델을 지원하지 않는 HW를 위해 JVM에 별도 구현 작업이 필요하다.
  • 약한 메모리 모델일수록 CPU 아키텍처 추세(멀티코어) 와 잘 어울린다.
    • 각 코어가 독립적으로 작업을 수행할 수 있기 때문에, 추가 코어의 이점을 최대한 활용할 수 있기 때문이다

JMM의 application 보호법

  • Happens-Before: 한 이벤트는 무조건 다른 이벤트보다 먼저 발생한다
  • Synchronizes-With: 이벤트가 객체 뷰를 메인 메모리와 동기화시킨다
  • As-If-Serial: 실행 스레드 밖에서는 명령어가 순차 실행되는 것 처럼 보인다
  • Release-Before-Acquire: 한 스레드에 걸린 락을 다른 스레드가 그 락을 획득하기 전에 해제한다

Java에서의 thread

  • Java에서 thread는 객체 상태 정보를 스스로 들고다닌다
  • thread가 변경한 내용은 메인메모리로 곧장 반영된다
  • 같은 데이터를 access하는 다른 thread가 다시 읽는다

동시성 라이브러리

Java의 동시성 라이브러리를 구성하는 핵심요소

  • 락, 세마포어
  • 아토믹스
  • 블로킹 큐
  • 래치
  • 실행자

개발자가 해당 라이브러리의 구현원리를 알고있다면 동시성 라이브러리를 최적으로 활용할 수 있다.

CAS

  • 예상되는 현재 값과 원하는 새 값, 메모리 위치(포인터)를 전달받아 두가지 일을 하는 아토믹 유닛이다
  1. 예상되는 현재 값을 메모리 위치에 있는 콘텐츠와 비교한다
  2. 두 값이 일치하면 현재 값을 원하는 새 값으로 교체한다

아토믹 유닛이란?
용어는 작업의 단위를 나타내며, 해당 작업은 중단되거나 나눌 수 없는 것으로 간주된다.
즉, 아토믹 연산은 완전히 수행되거나 전혀 수행되지 않는 성질을 가지고 있어서, 이러한 연산 중에는 어떠한 다른 연산도 끼어들 수 없다

최신 프로세서가 꽂힌 대부분의 하드웨어에 CAS 기능이 구현되어 있다.
CAS 하드웨어는 sun.misc.Unsafe 클래스를 통해 access 한다.

Unsafe

Unsafe는 내부 구현 클래스이다.
Unsafe는 표준 자바 플랫폼 API가 아니다.
이 클래스를 엄밀히 말해 핫스팟 VM에 직접 연결되고 깨질 우려가 높다. 하지만 Unsafe는 거의 모든 주요 프레임워크의 구현 핵심부를 차지하게 되었다.

Unsafe로 할 수 있는 일들은 다음과 같다

  • 객체는 할당하지만 생성자는 실행하지 않는다
  • 원메모리(raw memory)에 액세스하고 포인터 수준의 연산을 수행한다
  • 프로세서별 하드웨어 특성(ex. CAS)을 이용한다

Unsafe로 할 수 있는 일을 해서 아래와 같이 고수준의 프레임워크 기능을 구현할 수 있다.

  • 신속한 (역)직렬화
  • thread-safe native memory access
  • 아토믹 메로리 연산
  • 효율적인 객체/메모리 레이아웃
  • 커스텀 메모리 펜스
  • 네이티브 코드와의 신속한 상호작용
  • JNI에 관한 다중 운영체제 대체물
  • 배열 원소에 volatile하게 access

Unsafe는 자바 SE 공식 표준은 아니지만, 워낙 업계에 활용도가 높아 사실상 표준이나 다름없다.

profile
BackEnd Developer

0개의 댓글