TIL-01

8 분 소요




List

ArrayList

동적배열을 기반으로 배열의 크기가 가득찰때마다 더 큰 배열을 생성하고 기존의 요소들을 새 배열로 복사한다.
데이터 검색에 유리하고 추가 / 삭제에 불리한 구현체이다.
대량에 데이터를 추가할 때 가능한 addAll 메서드로 한번에 추가하는 것이 내부적인 배열복사로 인한 성능감소에 효과적이다.

  • 장점
    • 배열기반으로 인덱스를 통한 빠른 접근이 가능하다. o(1)
    • 연속된 메모리 공간에 데이터를 저장한다.
  • 단점
    • 리스트 중간에 요소를 추가 / 삭제 할때 나머지 요소들을 이동시켜야 하므로 o(n)의 시간복잡도를 가진다.

LinkedList

불연속적으로 존재하는 데이터를 서로 연결한 형태인 노드의 연결로 이루어져 있고
데이터를 저장하는 각 노드가 이전노드와 다음노드의 상태만을 알고있다.

데이터 추가 / 삭제에 유리하고 검색에 불리한 구현체이다.

  • 장점
    • 데이터 추가 / 삭제에 이전노드와 다음노드의 상태만 변경하면 되기 때문에 o(1)의 시간복잡도를 가진다.
    • 이전요소와 다음요소의 참조를 모두 가지고 있어 양방향 순회가 가능하다.
  • 단점
    • 데이터 검색시에 최악의 경우 o(n)의 시간복잡도를 가진다.
    • 각 요소가 추가적으로 이전노드와 다음노드 2개의 참조를 가지고 있기 때문에 ArrayList보다 더 많은 메모리를 사용한다.





JIT Compiler

  • 런타임 시 바이트 코드를 바이너리 코드로 컴파일 하여 애플리케이션의 성능을 향상시키는데 도움을 준다.
  • 컴파일된 바이트코드는 JVM내에서 인터프리터 방식으로 실행되지만 많이 사용되는 코드는 JIT컴파일러의 대상이 되어서 컴파일 된다.
  • 기본적으로 JIT 컴파일러는 사용으로 설정된다.
  • -XcompilationThreads 옵션으로 JIT컴파일 스레드 개수를 조절할 수 있다.

동작원리

  1. JVM은 인터프리터 방식으로 바이트 코드를 실행한다.
  2. 실행이 빈번하게 발생하는 코드(핫스팟)을 식별한다.
  3. 핫스팟으로 식별된 바이트코드 영역은 JIT컴파일러에 의해 기계어로 컴파일된다.
  4. JIT컴파일러에 의해 변환된 기계어는 인터프리터를 거치지 않고 직접 실행된다.
  5. 한번 JIT컴파일된 코드는 JVM이 관리하는 코드캐시 또는 메소드 캐시라 불리는 메모리영역에 캐싱되어, 같은코드가 실행될 때 빠르게 접근할 수 있다.

핫스팟탐지

다음은 JVM의 핫스팟 탐지 매커니즘 중 하나이다.

  • JVM은 각 메서드가 호출될 때마다 그 호출 횟수와 실행에 소요된 시간을 추적하고 특정 호출 횟수에 도달하거나 실행시간이 임계값을 초과하면 해당 메서드는 JIT 컴파일의 대상이 된다.
  • 호출 횟수가 기준에 미치지 못하면 JIT컴파일 되지 않을 수 있다.
  • 이미 JIT컴파일의 대상이 되었어도 JVM은 실행 시간 동안 프로그램의 동작을 지속적으로 분석하고, 특정 조건하에서 메서드를 다시 컴파일하거나 더 고급의 최적화 기법을 적용할 수 있다.





Graal VM

더 공부해야 하는 부분 - 확실하지 않음
HotSpot VM의 서버 컴파일러인 C2 컴파일러가 더이상 유지보수가 어려워서 대안으로 나온

다양한 언어를 지원하는 고성능의 범용 가상머신

  • JVM언어 뿐 아니라 다양한 프로그래밍 언어를 지원한다.
  • 동적인 JIT 컴파일 방식과 정적인 AOT 컴파일 방식을 모두 제공한다.
  • Ahead-of-Time(AOT) 컴파일을 통해 만든 네이티브 이미지는 JVM 없이도 실행이 가능하다.





Class Loader

바이트 코드를 JVM이 읽을 수 있도록 JVM 메모리 영역에 런타임중에 적재하는 역할을 한다.

순서

  • 로딩
    • class 파일을 찾아 JVM 메모리에 로드한다.
    • 각 클래스는 JVM내에서 고유한 java.lang.Class 로 표현된다.
  • 링킹
    • 검증 : 클래스파일의 유효성을 체크한다. 이때 믿을 수 있는 클래스 파일만 있는 경우 성능향상을 위해 -Xverify:none 옵션으로 검증하지 않을 수 있다.
    • 준비 : 클래스가 필요로 하는 메모리를 할당한다. (static 변수와 기본값)
    • 해석 : 심볼릭 메모리 주소를 실제 힙 메모리 영역에 있는 인스턴스에 대한 주소로 바꾼다.
  • 초기화
    • 메모리를 할당한 static 변수와 기본값에 실제 값들을 할당한다.

구조

  • 부트스트랩 클래스로더
    • JVM이 가장먼저 사용하는 클래스로더
    • jre / lib / jar 및 기타 핵심 라이브러리 같은 JDK 내부 클래스를 로드한다
  • 확장 클래스 로더
    • 부트스트랩 클래스 로더를 부모라 가진다.
    • 확장 자바클래스들을 로드한다.
  • 시스템 클래스 로더
    • classpath에 있는 즉, 우리가 만든 어플리케이션의 클래스들을 로드한다.





Logger vs System Out stream(err,out)

System Out

public class PrintStream extends FilterOutputStream implements Appendable, Closeable {

    public void println() {
        newLine();
    }

    private void newLine() {
            try {
                synchronized (this) {
                    ensureOpen();
                    textOut.newLine();
                    // ...
  • I/O 작업을 수행하며, 시스템 호출을 통해 운영체제의 커널로 전달된다.
  • Write 작업은 PrintStream 클래스의 println 메서드는 synchronized 로 동기화 된다.
  • 여러 스레드가 동시에 출력을 시도할 때 상당한 대기 시간과 성능 저하가 발생할 수 있다.

Logger

  • 마찬가지로 I/O 작업을 수행한다.
  • Write 작업이 별도의 로깅스레드에서 비동기로 실행된다.
  • 로그 처리는 로그 시스템이 관리하는 내부 큐와 별도의 처리 스레드를 통해 이루지기 때문에 직접적인 블로킹이 발생하지 않는다.
  • 로그 출력 레벨을 사용할 수 있다.





HashTable vs ConcurrentHashMap

HashTable

  • 모든 public 메서드 레벨에서 synchronized 를 사용한 동기화 사용
  • 모든 연산이 단일 락을 사용함으로써 한 시점에 하나의 스레드만 맵에 대한 연산을 수행할 수 있다.

ConcurrentHashMap

  • volatile을 사용하여 변수의 최신값을 보장하고, 추가적으로 lock 알고리즘을 사용한다.
  • 내부구조는 배열과 연결 리스트의 조합으로 이루어져 있는데, 이 배열의 각 요소를 버킷 이라고 하며, 각 버킷에는 키-값 쌍을 저장하는 노드들이 연결 리스트나 트리 형태로 연결되어 있다.
  • 쓰기작업이 발생할 때, 해당 키가 위치하는 버킷 또는 해당 버킷내의 노드 에만 락을 적용한다.
  • 한 스레드가 한 버킷에서 쓰기 작업을 수행하는 동안 다른 스레드는 다른 버킷에서 동시에 쓰기 작업을 수행할 수 있다.





Thread Local

  • 스레드 단위로 변수를 할당해 스레드 내부에서 접근할 수 있도록 한다.
  • 다른 스레드와의 변수 공유로 인한 동시성 문제를 방지할 수 있다.
  • 사용자 인증 정보, 디비 연결, 세션 정보 등 스레드마다 다른 컨텍스트 정보를 저장해야 할 때 유용하다.
    • ex) Spring Security
  • 스레드풀에서 스레드를 재사용할땐 꼭 사용 후 remove()를 해줘야 한다.





PermGen

  • JDK 1.7 까지
    • PermGen
      • 클래스와 메서드에 대한 메타 정보, 상수 풀, 정적 변수등을 저장한다.
      • JVM시작 시 한번 설정되면 런타임 중 변경할 수가 없고 이로인해 oom이 발생
  • JDK 1.8 이후
    • Metaspace (메서드 영역)
      • class 메타정보, static 등의 정적 정보가 위치
      • native memory 영역으로 OS가 크기를 조절한다.
  • constant pool이 heap으로 옮겨간 후의 장점
    • 힙영역에 속함으로써 크기가 유연하게 확장될 수 있음





String + 연산 버전별 차이점, 컴파일러가 어떻게 최적화 하는지

  • JDK 1.4 이하
    • 연산할 때 마다 새로운 String 객체가 생성되고 이전 문자열들을 복사하여 새 객체에 넣는 방식
    • 이는 많은 임시 객체 생성과 메모리복사를 초래한다.
  • JDK 1.5
    • +연산 시 StringBuilder를 사용하도록 컴파일러가 최적화 됨
    • 연산을 할때마다 새로운 String 객체를 생성하는 대신 StringBuilder객체를 재사용 하여 효율적
  • JDK 1.9
    • +연산 코드를 발견하면 컴파일러는 이를 invokedynamic 호출로 변환한다.
    • invokedynamic는 컴파일이 아닌 런타임 시점에 해당 연산에 최적화된 메서드를 호출한다.
      • ex) StringBuilder, StringConcatFactory 등





Singleton Pattern 구현 방법

Eager Initialization

class Singleton {
	private static Singleton singleton = new Singleton(); 
	
	private Singleton() {}
	
	public static Singleton getInstance() {
		return singleton;
	}
}
  • 클래스 로더가 로딩하는 시점에 인스턴를 생성
  • 해당 인스턴스를 사용하지 않더라도 인스턴스를 생성하기 때문에 낭비가 될 수 있음

Lazy Initialization

class Singleton {
	private static Singleton singleton; 
	
	private Singleton() {}
	
	public static Singleton getInstance() {
		if (singleton == null) singleton = new Singleton();
		return singleton;
	}
}
  • 싱글톤 인스턴스를 호출할 때 인스턴스를 생성
  • 멀티스레드 환경에서 동기화 문제가 발생

Thread Safe Singleton

class Singleton {
	private static Singleton singleton; 
	
	private Singleton() {}
	
	public static synchronized Singleton getInstance() {
		if (singleton == null) singleton = new Singleton();
		return singleton;
	}
}
  • thread safe
  • synchronized의 성능 하락

Inner Class (권장)

class Singleton {
	private Singleton() {}

	private static class SingletonHelper {
		private static final Singleton SINGLETON = new Singleton();
	}
	
	public static Singleton getInstance(){
	    return SingletonHelper.SINGLETON;
	}
}
  • inner class를 두어 싱글톤 인스턴스를 갖게 함
  • 클래스로드 시점에 인스턴스를 생성하지도 않으면서 thread safe 하지만 syncjronized를 사용하지 않음





GC 종류

Serial GC

Parallel GC

G1 GC





byte code instrument

  • 런타임 또는 로드때 자바 바이트코드를 수정해 소스파일의 수정없이 원하는 기능을 부여하는 기법
  • 모니터링 툴들이 대부분 bci기능을 이용하고 있으며, 어플리케이션의 수정없이 성능측정에 필요한 요소를 삽입할 수 있다.
  • Spring AOP 에 사용된다.
  • java.lang.instrument 패키지를 통해 에이전트가 클래스 파일의 바이트 코드를 조작할 수 있다.





jvm 튜닝

  • GC 알고리즘 선택
  • Heap 사이즈 및 Heap 세부영역 사이즈 (young, old, survivor 등)
  • MetaSpace 사이즈
  • JVM 모드 (server / client)
  • 힙 덤프





ConcurrentModificationException

  • 발생이유
    • 순회중인 컬렉션의 구조를 변경하면 컬렉션 내부 상태와 순회중인 상태 사이에 불일치가 발생하기 때문에 발생한다.
    • 컬렉션 내부에 modCount 라는 변수가 컬렉션의 수정 횟수를 추적한다.
  • 해결방법
    • Iterator 사용
    • java.util.concurrent 사용 ex) ConcurrentHashMap
    • 스트림 API 사용





data locality

  • 캐시 히트율을 높이기 위해 필요하다.
  • 시간 지역성 : 최근에 참조된 주소의 내용은 곧 다음에 다시 참조되는 특성
  • 공간 지역성 : 메모리내에서 인접한 위치에 저장된 데이터가 연속적으로 접근될 가능성이 높다.





해시충돌

  • 해시함수의 입력값은 무한하지만 출력값은 유한하기 때문에 발생한다.
  • 자바8부터는 Chaining 방식의 Linked List를 사용하다가 임계수치를 넘으면 red-black-tree를 활용하여 사용한다.

해소법

Chaining

  • 해시충돌이 발생하면 연결리스트(Linked List)로 데이터들을 연결하는 방식
  • 장점
    • 단순하게 연결리스트만 사용하면 된다.
  • 단점
    • 해시값과 연결된 연결리스트를 검사해야 하므로 성능저하


Open Addressing

  • 해시충돌 발생 시 다른 버킷에 데이터를 삽입하는 방식
  • 선형탐색 : 일정한 간격으로 버킷에 데이터를 삽입
  • 제곱탐색 : 제곱만큼 건너뛴 버킷에 데이터를 삽입
  • 이중해시 : 다른 해시함수를 한번 더 적용한 결과를 사용
  • 장점
    • 체이닝처럼 포인터와 추가메모리가 필요없다
  • 단점
    • 데이터가 많을 수록 관리가 힘듬





TCP 3-way-handshake

TCP 연결의 신뢰성과 순서보장을 위해 필수적인 절차

  • SYN : 연결이 이루어지도록 요청
  • ACK : 요청을 확인했다는 응답
    1. 클라이언트가 서버에 연결을 시도하면서 SYN 패킷을 보낸다.
    2. 서버가 클라이언트의 SYN 요청을 받으면 서버의 SYN응답과 클라이언트의 요청에 대한 ACK 응답인 SYN+ACK 패킷을 클라이언트에게 보낸다.
    3. 클라이언트는 SYN+ACK 패킷을 받고 서버에게 SYN 요청에 대한 응답으로 ACK 패킷을 보낸다.


4-way-handshake

세션을 종료하기 위해 수행되는 절차

  1. 클라이언트가 연결을 종료하겠다는 FIN 플래그를 전송한다.
  2. 서버는 요청을 받고 ACK 응답을 보낸다.
  3. 서버는 데이터를 모두 보내고 통신이 끝나면 연결이 종료되었다는 FIN 플래그를 전송한다.
  4. 클라이언트는 종료메시지를 확인했다는 ACK를 보낸다.





TCP 흐름제어 / 혼잡제어

흐름제어

송신측과 수신측의 데이터 처리 속도 차이를 해결하기 위한 기법 - 수신측이 너무 많은 패킷을 수신받지 않도록 하기 위함
송신측의 전송 속도가 너무 빨라 한 번에 수많은 패킷을 수신받아버린다면 수신측의 버퍼가 가득차 손실되는 패킷들이 발생한다.

Stop-And_wait

매번 전송한 패킷에 대한 확인 응답(ACK)을 받아야 그 다음 패킷을 전송하는 기법

Sliding Window (Go-Back-n ARQ)

수신 측에서 설정한 윈도우 크기만큼 송신 측에서 확인 응답(ACK) 없이 패킷을 전송할 수 있게 하여 데이터 흐름을 동적으로 조절하는 제어 기법

  • 윈도우 크기
    • 최초의 윈도우 크기는 3-way-handshake 를 통해 수신측 윈도우 크기로 설정되며 이후 수신측 버퍼에 남아있는 공간에 따라 변한다.
    • 윈도우 크기는 수신측에서 송신측으로 응답(ACK)를 보낼때 TCP 헤더에 담아서 보낸다.


혼잡제어

하나의 라우터에 데이터가 몰리면 라우터가 처리하지 못하는데 이때 송신측에서 재전송을 하고 결국 네트워크를 혼잡하게 만들고 오버플로우나 데이터손실을 발생시킨다. 혼잡제어는 송신측에서 데이터 전송속도를 제어하는 기법이다.

AIMD

  • 처음 패킷을 하나씩 보내고 문제가 없으면 윈도우 크기를 1씩 증가시켜 전송한다. 전송에 실패하면 윈도우 크기를 반으로 줄인다.
  • 네트워크가 혼잡해 지고 나서 대역폭을 줄이는 방식이다.

Slow Start (느린시작)

  • 각각의 ACK 패킷마다 윈도우사이즈를 1씩 증가시킨다. 즉, 1-2-4-8 이런식으로 윈도우 크기가 늘어난다.
  • 전송에 실패하면 윈도우사이즈를 1로 줄인다.

Fast Retransmit(빠른 재전송)

  • 수신측에서 순서대로 패킷을 받지 않을경우 마지막 패킷의 다음 패킷 순번을 ACK 패킷에 실어 보낸다.
  • 이런 중복 ACK를 3개 받으면 재전송이 이루어지고, 혼잡상황으로 간주하고 윈도우 사이즈를 줄인다.

빠른 회복 (Fast Recovery)

  • 빠른 회복은 혼잡한 상태가 되면 윈도우 크기를 1로 줄이지 않고 반으로 줄이고 선형 증가시키는 방법이다.
  • 이 방법을 적용하면 혼잡 상황을 한 번 겪고나서부터는 AIMD 방식으로 동작한다.





HTTP 상태코드

200대

  • 200
  • 201

    400대

  • 400
  • 401
  • 402
  • 403
  • 404
  • 405

    500대

  • 500
  • 504





HTTP 버전

1.0

1.1

2.0

3.0

댓글남기기