자바 최적화

11. 자바 언어 성능 향상 기법

p327

참조형 필드는 힙에 레퍼런스로 저장됩니다. 객체가 순차적으로 저장된다고 대충 말하지만, 사실 컨테이너에 저장되는 건 객체 자신이 아니라, 객체를 가리키는 레퍼런스입니다. 그러므로 C/C++ 형식의 배열이나 벡터를 사용하는 것만큼 성능을 얻을 수는 없습니다.

p329

ArrayList는 고정 크기 배열에 기반한 리스트입니다. 배킹 배열의 최대 크기만큼 원소를 추가할 수 있고 이 배열이 꽉 차면 더 큰 배열을 새로 할당한 다음 기존 값을 복사합니다.
  • 크기 조정이 발생

    • 비용이 꽤 될 것 그러므로 크기를 정확히 결정할 수 있다면 결정하고 객체를 생성하는 것이 나음

    • 벤치마크로 알아볼 수도 있을듯?

p331

LinkedList의 고유 기능이 꼭 필요한 경우가 아니라면, 특히 랜덤 엑세스가 필요한 알고리즘을 구사할 떄는 ArrayList를 권장합니다. 

p333

HashMap은 처음에는 버킷 엔트리를 리스트에 저장합니다. 값을 찾아려면 키 해시값을 계산하고 equals() 메서드로 리스트에서 해당 키를 찾습니다. 키를 해시하고 동등성을 기준으로 리스트에서 값을 찾는 매커니즘으로 키 중복은 허용되지 않습니다. 같은 기를 넣으면 원래 HashMap에 있던 키를 치환합니다.
  • 자바 hashmap에서 키를 통해 값을 찾는 일련의 과정

p334

HashMap의 용량을 현재 생성된 버킷의 수(디폴트 16)를, loadFactor는 버킷 용량을 자동 증가(2배) 시키는 한게치 입니다. 용량을 2배 늘리고 저장된 데이터를 재배치한 다음, 해시를 다시 계산하는 과정을 재해시rehash라 합니다.
  • 해시맵에도 용량이 있고, 보통 2배 늘리고 재배치하는 과정을 가져가는 듯.

  • 다만 생성자에 전달되는 initialCapacity, loadFactor 두 매개 변수를 통해서 위 과정을 좀 더 효율적으로 할 수 도 있을듯

p335

  • 레드블랙트리는 삽입/삭제/검색의 시간복잡도를 O(logN) 을 보장

p337

  • 즉 HashSet은 내부적으로 HashMap을 사용하고 있으며, Map의 value값에는 더미 객체가 들어가는 구조라는 것

p340

p347

  • C와는 다르게 자바는 가비지 콜렉터가 객체에 대한 생명주기를 관리함

  • 문제는 가비지 컬렉터가 언제 이 객체를 청소할지 모른다는 것

    • 그래서 Obejct.finalize() 는 java9 이후 부터 deprecated됨

12. 동시 성능 기법

p361

  • 업데이트 소설

    • 멀티 스레딩 환경에서 발생할 수 있는 문제

    • A 스레드에서 업데이트 했는데, 다른(B) 스레드에는 그 업데이트 상황이 반영되지 않아 결국엔 A 스레드에서 업데이트한 것이 없어지는 현상

  • sychronized 블록 없이 레퍼런스를 여러 스레드에서 공유할 경우(그리고 수정한다면) 문제가 발생할 가능성이 다분

    • 더 문제는 문제가 발생할 수도 혹은 발생하지 않을 수도 있다는 점

    • 버그 재현이 힘들 수도

p365

  • JMM의 메모리 모델 (고수준)

    • 강한 메모리 모델, 전체 코어가 항상 같은 값을 바라봄

    • 약한 메모리 모델, 코어 마다 다른 값을 바라볼 수 있고 그 시점을 제어하는 특별한 캐시 규칙이 있다.

  • 자바에서는 강한 메모리 모델이 아닌 약한 메모리 모델을 선택

    • 왜?

      • 언어 자체가 아키텍쳐에 독립적인 이라는 점 (강한 메모리 모델을 지원하지 않는 하드웨어의 경우.. 이 부분을 지원하기 위해 어떻게 해야할지 난감한듯)

      • 멀티코어 체제에 부적절하기 떄문

p 367

  • JMM의 기본개념

  • 자바에서 스레드?

    • 객체 상태 정보를 스스로 들고 다니며,

    • 스레드가 변경한 내용은 메인 메모리에 곧장 반영괻고,

    • 같은 데이터를 액세스하는 다른 스레드가 읽는 구조

  • 자바의 Synchronized keyword?

    • JMM 기본 개념의 Synchronizes-With 와 동일

    • 즉 모니터를 장악한 스레드의 로컬 뷰가 메인 메모리와 동기화 되었다는 뜻

  • 자바에서 동기화되지 않는 액세스는, 그러니까 이 스레드에서 변경한 부분을 언제 다른 스레드에서 읽을 수 있는지 보장하지 않음.

    • 약한 메모리 모델이기 때문인듯

p368

  • 그러면 Synchronized 락의 한계점?

  • 쓰기 작업에만 Synchronized 를 적용하면, lost update 현상이 일어난다.

    • 즉 읽기 작업을 할 때 변경된 값을 읽어 오리라는 보장이 없다.

p371

Unsafe

  • sum.misc.Unsafe 는 내부 구현 클래스

    • 저수준 하드웨어 명령어에 액세스할 수 있음

  • 표준 자바 플랫폼 API가 아니다.

  • 애플리케이션 개발자가 이 클래스를 직접 이용할 일은 거의 없다.

  • 할 수 있는 일?

    • 객체는 할당, 생성자는 실행하지 않음

    • raw memory에 액세스 하고 포인터 수준의 연산을 수행

    • 프로세스별 하드웨어 특성 (e.g. CAS)을 이용

  • 이를 통해

    • 직렬화, 역직렬화

    • 스레드 안전한 네이티브 메모리 액세스

    • atomic 메모리 연산

    • 효율적인 객체/메모리 레이아웃

    • custom memory fence

    • 네이티브 코드와의 상호작용

    • JNI에 관한 다중 운영체체 대체물..

    • 배열 원소에 volatile하게 액세스..

  • p373에서 Unsafe.getUnsafe() 로 직접적으로 사용하면 SecurityException 발생

    • 스프링을 사용하면 UnsafeUtils 로 접근하도록 하자

    • https://stackoverflow.com/questions/13003871/how-do-i-get-the-instance-of-sun-misc-unsafe

java.util.concurrent 락

  • lock()

    • 락을 획득하고, 락을 사용할 수 있을 때 까지 블록킹

  • newCondition()

    • 락 주위에 조건을 설정해 좀 더 유연하게 락을 확용

    • 락 내부에서 관심사 분리(읽기 혹은 쓰기)가 가능하다

  • tryLock()

    • 락을 획득하려고 시도(타임아웃 설정가능)

    • 덕분에 스레드가 락을 사용할 없는 경우에도, 처리를 진행할 수 있음.

    • 논 블로킹 방식

  • unlock()

    • 락 해제

  • ReentrantLock

    • Lock의 주요 구현체

    • 내부적으로는 int 값이 compareAndSwap(0 함)

  • LockSupport

    • 스레드에게 permit을 발급 (퍼밋 == 세마포어..?)

    • 만약 발급할 수 있는 permit 이 없다면 스레드는 기다려야함

    • 0, 1 binary semaphore만을 permit으로 발급함

    • 스레드가 permit을 발급받지 못한 경우 parking 되고

    • 유효한 permit을 발급받을 수 있다면 해당 스레드는 unparking됨

    • 이 클래스가 Thread.suspend() -> parking, Thread.resume() -> unparking을 대체 한다

  • ReentrantReadWriteLock

    • 읽기와 쓰기에서 다른 락을 사용하는 방법

    • 즉 읽기 작업 중에는 다른 읽기 스레드는 블로킹 되지 않도록 하지만

    • 쓰기 작업 중에는 읽기나 혹은 쓰기 스레드는 블로킹 될 것

세마포어

  • '최대 ~개의 객체까지만 액세스를 허용한다' 는 전제하에 정해진 수량의 permit으로 액세스를 제어하는 것

  • permit이 하나 뿐인 세마포어(binary semaphore)는 뮤텍스 mutex와 동등

    • 뮤텍스는 뮤텍스가 걸린 스레드가 해제할 수 있는 반면

    • 세마포어는 비소유 스레드도 세마포어를 해제할 수 있는 점이 다르다

p381

  • Happens-Before?

    • 한 이벤트는 무조건 다른 이벤트보다 먼저 발생한다.

p384

Fork/Join

  • ForkJoinPool 클래스는 두가지 특성이 있음

    • 하위 분할 태스크를 효율적으로 처리할 수 있음

    • 작업 빼앗기 알고리즘을 구현

  • 하위 분할 태스크?

    • 자바 스레드보다 가벼운, 스레드와 비슷한 엔티티

    • ForkJoinTask 클래스가 지원하는 기능

      • 자기 자신을 더 작은 서브 태스크를 분할하는 능력이 핵심

    • 적은 수의 스레드가 아주 많은 태스크/서브 태스크를 담당해야하는 경우 사용

p390

  • 자바 스트림은 데이터 소스에서 원소를 퍼 나르는 불변 데이터 시퀀스

  • parallelStream() 을 이용하면 병렬로 데이터를 작업 후, 그 결과를 재조합 할 수 있음

    • 실제로 컬렉션이 작을수록 직렬 연산이 병렬 연산보다 훨씬 빠르다

    • 그러므로 항상 패러럴스트림을 사용할 때는 성능 테스트를 해봐야한다

액터 기반 기법

  • actor 실행기

    • 가번적인 상태는 일체 공유하지 않고 오직 불변 메세지를 통해서만 액터끼리 상호 교류함

    • 이를 통해 상태 관리

  • 전통적인 락킹 체계보다 액터가 더 좋다. 왜?

    • 락을 쓰면 데드락..

    • 락을 쓰면 CPU 처리율이 떨어질 수도..

Reference

  • http://www.yes24.com/Product/Goods/72161685

Last updated