24.02.07 Java 관련 2
1. GC
- 가비지 컬렉터란, 힙 메모리 관리를 위해 참조되고 있지 않은 객체들을 메모리에서 삭제하는 역할
- 객체는 힙영역에 저정되고 스택 영역에 이를 가르키는 주소값이 저장되는데 힙영역에서 자신을 가르키는
주소값이 없으면, 참조되고 있지 않는다고 판단
1) GC 동작 과정
- 객체가 생성되면 메모리를 young 영역에 저장
- 객체를 최초 생성하면 Young 영역에 Eden 영역에 위치
- Eden 영역에서 Minor GC 가 발생하면 참조 중인 객체는 1번 Survivor 영역으로 이동
- 1번 Survivor 영역에서 Minor GC 가 발생하면 참조 중인 객체는 2번 Survivor 영역으로 이동하고
1번 Survivor 영역은 비게 됨
- Young 영역에서 오래 살아남은 객체는 Old 영역으로 이동
- Old 영역에 있는 객체는 Major GC 가 발생했을 때 참조 여부에 따라 유지되거나 제거됨
2) GC 알고리즘 종류
(1) Serial GC
- 가장 단순한 방식
- 싱글 스레드로 동작하고 그만큼 느리다
- Mark - sweep - compact 알고리즘 (mark 하고, sweep(지우고)하고, compact(빈공간을 채워넣음))
- 적은 메모리와 CPU 코어 갯수가 적을 때 적합
(2) Paraller GC
- Java8 의 기본 GC
- Serial GC 와 알고리즘은 같지만 GC 를 처리하는 스레드가 여러개
- 메모리와 코어가 충분할 때 적합
(3) Paraller Old GC
- Paraller GC 는 Young 영역에서만 멀티스레드를 사용하지만 이건 Old 영역까지 멀티쓰레드를 사용
- Paraller GC 에서 Old GC 알고리즘을 개선한 버전
3) GC 장단점
(1) 장점
- 메모리 누수를 막을 수 있다
- 해제된 메모리에 접근하는 오류와 해제된 메모리를 한번 더 해제하는 이중 해제를 막을 수 있다
(2) 단점
- GC 메모리 해제 타이밍을 개발자가 정확하게 알기 힘들다
- 실시간성이 강조되는 프로그램의 경우 GC 에게 메모리를 맡기는 것은 알맞지 않을 수 있다
2. 정적 타입, 동작타입 언어의 장단점
1) 정적 타입 언어
- 컴파일 때 미리 타입을 결정하기 때문에 실행속도가 빠르고 타입 에러로 인한 문제점을 초기에
발견할 수 있어 안전성이 높다
- 매번 코드 작성시 변수형을 결정해줘야 하는 번거로움이 있다
2) 동적 타입 언어
- 런타임까지 타입에 대한 결정을 끌고 갈 수 있기 때문에 유연성이 높고, 컴파일시 타입을 명시해주지
않아도 되기 때문에 빠르게 코드를 작성할 수 있다
- 실행 도중에 변수에 예상치 못한 타입이 들어와 타입에러가 발생할 수 있다
- 런타임시 확인할 수 밖에 없기 때문에 코드가 길고 복잡해질 경우 타입 에러를 찾기가 어려워짐
3. 스크립트 언어와 컴파일 언어 차이
1) 스크립트 언어
- Javascript, Pytion 이 대표적
- 인터프리터가 한 라인 한라인 기계어로 번역하며 실행
- 컴파일 에러라고 부르는 문법 오류를 사전에 방지하지 못하기 때문에 주의
- 운영체제를 신경 쓸 필요 없이 한번만 적성하면 됨
2) 컴파일 언어
- C, C++ 이 대표적
- 실행 전 소스코드를 컴파일하여 기계어로 변환 후 해당파일 실행
- 이미 기계어로 변환된 것을 실행하므로 비교적 빠르다
- 컴파일 과정을 거쳐 기계어 코드로 번역이 되기 때문에 사전에 검증을 할 수 있고 최적화를 해줄 수 있다
- 운영체제에 따라 다르게 작업해야 함
4. Java 코드 컴파일 과정
- 개발자가 자바 소스 코드를 작성하면 자바 컴파일러가 자바 소스파일을 컴파일
이때 나오는 파일은 자바 바이트 코드 파일로, 아직 컴퓨터가 읽을 수 없는 자바 가상 머신이 이해할 수 있는 코드
- 컴파일된 바이트 코드를 JVM 의 클래스 로더에 전달
- 클래스 로더는 동적로딩을 통해 필요한 클래스들을 로딩 및 링크하여 JVM 의 런타임 데이터 영역에 올림
- 실행 엔진은 JVM 메모리에 올라온 바이트 코드들을 명령어 단위로 하나씩 가져와서 실행
이때, 실행엔진은 바이트 코드를 각 OS 가 실행할 수 있는 기계어로 변환
* 바이트 코드 : 특정 하드웨어가 아닌 가상 머신에서 돌아가는 실행 프로그램을 위한 이진 표현법
대부분 명령어는 0 개 이상의 매개변수를 갖는 1바이트 크기를 가지고 있다
5. 클래스 로더 동작 과정
- 클래스 파일을 가져와서 JVM 의 메모리에 로드
- 자바 언어 명세 및 JVM 명세에 명시된 대로 구성되어있는지 검사
- 필드, 메서드, 인터페이스 등 메서드 클래스가 필요로 하는 메모리 할당
- 클래스의 상수 풀 내 모든 심볼릭 레퍼런스를 다이렉트 레퍼런스로 변경
- 클래스 변수들을 적절한 값으로 조기화
* 심볼릭 레퍼런스 : 참조할 때, 클래스의 특정 메모리 주소를 참조 관계로 구성한 것이 아닌 참조하는 대상의
이름을 지칭
Class 파일이 JVM 에 올라가게 되면 심볼릭 레퍼런스는 그 이름에 맞는 객체의 주소를 찾아서
연결하는 작업을 수행하므로 실제 메모리 주소가 아니라 이름만을 가짐
* 다이렉트 레퍼런스로 변경이라는 의미? : 실제 메모리 주소 값으로 변경해 주는 작업을 의미
6. 동적 로딩?
- 프로그램을 실행할 때 필요할 때마다 동적으로 메모리를 생성하고 필요없는 메모리는 자동으로 소멸시키는 것
- 일부 클래스가 변경되어도 전체 어플리케이션을 다시 컴파일 하지 않아도 되기에, 변경사항이 발생해도
비교적 적은 작업만으로 처리 할 수 있다
7. 실행 엔진이 바이트코드를 실행하는 과정
1) 인터프리터 방식
- 바이트코드 프로그램은 보통 한번에 하나의 명령어를 읽은 후 실행
- 한줄 씩 수행하기 때문에 느림
2) JIT 컴파일러
- 인터프리터 방식을 보완하기 위해 도입
- 인터프리터 방식으로 실행하다가 적절한 시점에서 바이트 코드 전체를 컴파일하여 네이티브 코드로
변경하고 이후에는 더 이상 인터프리팅 하지 않고 네이티브 코드를 직접 실행
- JIT 컴파일러를 사용하는 JVM 은 내부적으로 해당 메서드가 얼마나 자주 수행되는지 체크하고,
일정 정도를 넘었을 때 컴파일 수행
- JIT 컴파일러로 인해, 자바는 네이티브 언어와 유사한 수준의 퍼포먼스를 낼 수 있게 됨
8 .기본타입 VS 참조타입
1) 기본타입 (primitive type)
- 종류는 byte, short, char, int, float, double, bollean 이 있다
- 실제 데이터 값 자체를 저장한다 데이터의 크기가 작고 고정적이기 때문에 메모리의 Stack 영역에 저장됨
- Null 을 다루지도 못하고 Generics 에 담기지도 못함
- 참조타입에 비교해서 갖는 장점은 성능과 메모리 이점이 있다
2) 참조타입 (reference type)
- 기본타입을 제외하고는 모두 참조타입임
- 4 byte 크기의 주소값이 들어감
- array 와 class 와 같은 참조 데이터 타입은 객체가 메모리 상에 위치한 주소를 저장, 데이터의 크기가
동적이기 때문에 동적으로 관리하는 Heap 영역에 저장
- 더이상 참조하는 변수가 없을 때 가비지 컬렉션에 의해 파괴 됨
- 데이터가 동적이지 않으면 성능과 메모리에 장점이 있는 기본타입을 먼저 고려해야함
참조타입은 Stack 메모리에는 참조 값만 있고 실제 값은 Heap 메모리에 존재하기 때문에 값이
필요할 때마다 접근 속도가 느려지기 때문
- 만약 Null 을 다뤄야 허거나 Generic 타입에서 사용되어야 하는 경우 사용
9. Java ThreadLocal
- Thread 내부에서 사용하는 지역변수
- 일반 변수 수명은 특정 코드 블록 범위내에서만 유효하지만 ThreadLocal 을 이용하면 쓰레드 영역에
변수를 설정할 수 있기 때문에 특정 쓰레드가 실행하는 모든 코드에서 그 쓰레드에 설정된 변수 값을
사용할 수 있게 됨
- 멀티 쓰레드 환경에서 각 쓰레드마다 get( ), set( ) 메서드를 통해 독립적으로 변수에 접근할 수 있다
1) 활용방법
- 한 쓰레드에서 실행되는 코드가 동일한 객체를 사용할 수 있도록 해준다
따라서, 쓰레드와 관련된 코드에서 변수를 공유할 때 파리미터 또는 리턴 값으로 정보를 제공해주지 않아도 된다
- 대표적인 활용예시는 Spring Security 에서 사용자 인증 정보를 사용하는 것
서버에서 클라이언트 요청들에 대해 각 쓰레드에서 처리하게 될 경우, 해당 유저의 인증 및 세션 정보나
참조 데이터를 저장하는데 ThreadLocal 이 사용됨
2) 내부 구조
- thread 정보를 key 로 하여 값을 저장해두는 Map 구조 이고 기본적 사용에는 get, set 메서드 사용
3) 사용시 주의 사항
- Thread 의 정보를 key 로 하여 Map 의 형식으로 데이터를 저장한 후 사용할 수 있는 자료구조이므로
TreadPool 을 사용해 thread 를 재활용 한다면 동일한 이전에 세팅했단 ThreadLocal 의 정보가
남아있어 원치 않은 동작을 할 수 있다
- ThreadPool 을 사용하는 경우 반드시 모두 사용후 ThreadLocal 값을 remove 메서드를 사용하여 값을
제거해주는것이 필요