728x90

자바의 하위 호환성

자바는 이전 버전이 상위 버전의 JVM에서 동작이 보장되지만

상위 버전의 기능을 하위 버전의 JVM에서 컴파일 할 수는 없다.

 

즉, 8버전의 기능을 17버전에서 구동할 수는 있어도

17버전에만 있는 기능을 8버전에서 구동할 수는 없다.

 

JDK 1.1

  • Inner Class
  • JavaBeans (자바로 작성된 소프트웨어 컴포넌트)
  • Remote Method Invocation (분산 애플리케이션 구축 시 사용되며 java.rmi 패키지에 제공된다)
  • Reflection

 

J2SE 1.2

  • Swing GUI
  • JIT
  • Collection Framework

 

J2SE 1.3

  • Hotspot JVM
  • Java Naming and Directory Interface (데이터 및 객체를 발견하고 참고하기 위해 사용)
  • Java Platform Debugger Architecture
  • JavaSound (오디오 재생 지원)

 

J2SE 1.4

  • assert (단언문)
  • 정규표현식
  • IPv6
  • Non-Blocking IO (nio)
  • XML API
  • JCE
  • JSSE
  • JAAS
  • Java Web Start

 

J2SE 5

  • Generics
  • Annotation
  • Concurrency API
  • Enumeration
  • Auto Boxing / Unboxing
  • 가변 길이 파라미터
  • Static import
  • java.util.Scanner

 

Java SE 6

  • 가비지 컬렉터 G1 GC를 테스트용으로만 사용하도록 추가
  • Scripting Language 지원
  • JDBC 4.0

 

Java SE 7

  • Diamond Oeprator <>
  • try 문에 선언된 자원들을 자동으로 관리
  • Switch 문에서 String 사용 가능해짐
  • Dynamic Language 지원
  • 이진수 리터럴, 숫자 리터럴에 _ 지원

 

Java SE 8 (LTS)

  • 람다 표현식
  • 메서드 참조
  • 인터페이스에 디폴트 메서드 구현 가능
  • Optional
  • Clock, ZoneId, LocalDate 등과 같은 날짜 API 추가
  • 스트림 API
  • Heap 영역의 PermGen 영역 제거

 

Java SE 9

  • Jigsaw (런타임의 모듈화 지원, 애플리케이션의 경량화, 성능 향상, 유지보수 용이)
  • java.net.http 패키지 추가
  • JShell (테스트 프로젝트나 main 메서드 없이 신속한 테스트를 가능하게 대화식 REPL 도구 제공)
  • <> 연산자를 익명 클래스에서도 사용 가능해짐
  • try-with-resources 개선
  • 인터페이스에서 Private 메서드 지원
  • Optional의 스트림 지원

 

Java SE 10

  • var 키워드로 지역 변수 타입 추론 가능
  • 병렬 처리 GC
  • 개별 쓰레드로 분리된 Stop-The-World

 

Java SE 11

  • HTTP 클라이언트 API를 정식으로 포함
  • String 클래스에 신규 메서드 추가
  • 람다의 파라미터로 타입 추론 사용 가능

 

Java SE 12

  • Switch 문의 문법 확장
  • GC 개선
  • 성능 개선

 

Java SE 13

  • yield 예약어 추가
  • Text-Block

 

Java SE 14

  • instanceof의 패턴 매칭
  • record 추가

 

Java SE 15

  • Sealed 클래스 추가 (상속 가능한 클래스를 지정할 수 있는 봉인 클래스)

 

Java SE 16

  • Unix 도메인 소켓에 연결 가능해짐

 

Java SE 17

  • Random Generator (예측하기 어려운 난수 생성 API)

'Java > Notion' 카테고리의 다른 글

JVM Warm Up  (0) 2024.03.04
Garbage Collection 파헤치기  (0) 2024.03.03
JVM 뜯어보기 (컴파일, 인터프리팅, JIT, GC, 메모리)  (0) 2024.03.02
자바의 컴파일 과정  (0) 2023.10.25
BigInteger  (0) 2023.05.08
728x90

Warm Up을 알아보기 전에 Warm Up이 필요한 이유에 대해 알아보겠다.

 

 

서버 배포 직후의 Latency 발생

서버 배포 직후 사용자가 요청을 보냈을 때 유독 응답이 느린 문제가 발생한다.

 

이러한 문제가 발생하는 원인은 크게 두 가지가 있는데

바로 클래스 로더의 지연 로딩과 JIT 컴파일러의 동작 방식 때문이다.

 

 

클래스 로더의 지연 로딩

클래스 로더는 클래스 파일을 찾아 메모리에 적재해 실행 가능하게 만들어주는데

이러한 동작을 자바 애플리케이션이 시작될 때 모두 메모리에 적재하는 것이 아니라

각 클래스들이 직접적으로 필요한 시점에 로딩을 하는 지연 로딩 방식을 사용한다.

 

즉, 서버 배포 직후에 사용자가 요청을 보내면 해당 요청을 처리하기 위해 필요한

클래스들은 당연히 아직 메모리에 적재가 되지 않은 상태일테고

이때부터 클래스 로더가  클래스들을 로딩하게 되기 때문에 배포 초기 Latency가 발생한다.

 

 

JVM의 컴파일 방식

JVM은 인터프리팅 방식과 컴파일 방식을 모두 사용하고 기본적으로 인터프리팅 방식을 사용한다.

 

그렇다면 컴파일 방식은 대체 언제 사용할까?

 

컴파일 방식은 모든 소스 코드를 한 번만 컴파일과 최적화를 해두면

이후에는 별도의 과정 없이 기계어로 읽을 수 있기 때문에

매번 코드를 하나하나 컴파일 하는 인터프리팅 방식보다 빠를 수 밖에 없다.

 

하지만 애플리케이션을 실행할 때 모든 class 파일을 컴파일 후에 실행하면

속도는 당연히 빠르겠지만 애플리케이션의 실행 시간이 많이 소요된다.

 

그래서 JIT 컴파일러는 실행 시 모든 코드를 컴파일 하지 않고 실행 중에 동적으로 컴파일을 진행한다.

 

 

JIT 컴파일러

JIT 컴파일러는 비유하자면 즐겨찾기 방식으로 컴파일을 진행하는데

애플리케이션 실행 중에 자주 실행되는 부분인 핫스팟을 컴파일 한다.

 

GC가 age를 기록하는 것과 비슷하게 JIT 컴파일러도 프로파일링이라는 과정을 통해

실행 중인 애플리케이션의 동작을 분석하고 기록하여 이를 토대로 핫스팟을 식별한다.

 

JIT 컴파일러는 이렇게 식별된 핫스팟을 컴파일해 코드 캐시라는 곳에 저장하여

인터프리팅 방식처럼 매번 하나하나 컴파일 할 필요 없이 저장된 것을 꺼내어 사용하기만 하면 된다.

 

 

JIT 컴파일러의 동작 방식

JIT 컴파일러는 Tiered Compliation이라는 여러 단계로 나뉜 컴파일 과정으로 동작하며

인터프리터와 C1, C2 두 개의 컴파일러로 이루어져 있다.

 

먼저, C1과 C2를 간단하게 살펴보겠다.

 

C1

가능한 빠른 실행 속도를 목적으로 하지만 최적화와 컴파일도 가능한 빠르게 하기 위해

제한된 수준으로만 최적화를 진행하는 컴파일러다.

 

특정 메서드가 일정치 이상 호출되면 C1 컴파일러에 의해 최적화 및 컴파일이 이루어 진다.

 

C2

C1 메서드로 최적화 및 컴파일 된 특정 메서드가 일정치 이상 호출되면 C2 컴파일러에 의해

C1보다 높은 수준의 최적화를 거쳐 컴파일이 진행된다.

 

C1과 C2 컴파일러로 컴파일된 코드는 모두 동일하게 코드 캐시에 저장된다.

 

Tiered Compliation

Tiered Compliation 과정은 0 ~ 4 Level로 구분된다.

 

  • Level 0 : Interpreted Code
    • 애플리케이션 실행 초기에는 모든 코드를 인터프리터를 통해 실행한다.
    • 당연히 컴파일 방식보다 성능이 낮다.
  • Level 1 : Simple C1 Compiled Code
    • 복잡도가 낮은 메서드를 대상으로 컴파일한다.
    • C2 컴파일러로 최적화 및 컴파일을 해도 유의미한 성능 향상이 기대되지 않는다.
    • 따라서 프로파일링도 진행하지 않는다.
  • Level 2 : Limited C1 Compiled Code
    • C2 컴파일러의 큐가 가득찬 경우 제한된 수준의 프로파일링과 최적화를 한다.
  • Level 3 : Full C1 Compiled Code
    • 일반적인 상황에서 수행되는 단계이다.
    • 최대 수준의 프로파일링과 최적화를 한다.
  • Level 4 : C2 Compiled Code
    • 장기적 성능 향상을 목적으로 C2 컴파일러가 최적화를 수행한다.
    • 이 단계에서 최적회된 코드는 완전한 최적화가 이루어져 프로파일링을 하지 않는다.

 

 

JVM Warm Up

Latency가 발생하는 두 가지 원인을 확인했으니 어떻게 해결할지는 단순하다.

 

  • 클래스 로더의 지연 로딩이 발생하지 않게 필요한 클래스들을 미리 사용해둔다.
  • C1, C2 컴파일러를 사용하기 위해 메서드를 미리 일정치 이상 호출해둔다.

 

결국 Warm Up은 후라이팬을 사용하기 이전에 예열을 하는 것처럼 JVM을 예열 시키는 방법이라고 볼 수 있다.

 

코드를 모두 실행해서 모든 클래스를 적재하고 메서드들도 예열시키기는 무리가 있기 때문에

자주 사용될 것으로 예상되는 부분들을 위주로 Warm Up을 진행해주면 된다.

 

각 컴파일러의 컴파일 임계치(Compile Threshold)는 C1은 1,500회, C2는 10,000회이다.

'Java > Notion' 카테고리의 다른 글

자바의 버전별 특징  (0) 2024.03.04
Garbage Collection 파헤치기  (0) 2024.03.03
JVM 뜯어보기 (컴파일, 인터프리팅, JIT, GC, 메모리)  (0) 2024.03.02
자바의 컴파일 과정  (0) 2023.10.25
BigInteger  (0) 2023.05.08
728x90

Garbage Collection

JVM의 Heap 영역 내에서 동적으로 할당했던 메모리 중 더 이상 사용하지 않는 메모리 객체들을 모아 주기적으로 자동으로 제거해주는 하나의 프로세스

 

개발자가 메모리를 직접 할당하고 해제하는 일에 신경을 쓸 필요가 없어져 개발에만 집중할 수 있게 해준다.

 

 

GC는 만능일까?

세상에 완벽한 것은 없기 때문에 당연히 단점이 존재한다.

 

우선 GC는 개발자가 제어할 수 있는 영역이 아니기 때문에 GC가 메모리를 언제 해제하는지 알 수 없으며,

GC가 동작하는 동안 관련 쓰레드를 제외한 모든 쓰레드가 멈추기(Stop The World) 때문에 오버헤드가 발생한다.

 

이런 STW 현상은 GC가 과하게 실행된다면 성능 하락으로 이어질 수도 있기 때문에

효율적으로 GC를 실행할 수 있게 최적화 하는 GC 튜닝 작업이 필요하다.

 

 

GC는 어떤 것들을 정리할까?

GC는 객체가 참조되어 접근할 수 있는지에 대한 도달성이라는 개념을 적용하여

객체의 레퍼런스가 존재하면 Reachable, 그렇지 않다면 Unreachable로 구분해 수거한다.

 

사실 당연한 이야기지만 참조가 되고 있지 않다면 앞으로 사용할 수 없는 객체이기 때문에

더 이상 필요 없는게 맞고 삭제하는게 당연하다.

 

 

GC의 청소 원리

청소는 Mark And Sweep 알고리즘으로 이루어진다.

 

청소할 객체를 식별한 후에 제거하면 제거된 공간이 빌 것이고

빈 공간을 앞에서부터 남아있는 객체들로 다시 채워나가는 방식으로 진행된다.

(Mark > Sweep > Compact)

 

우선 Mark 과정은 그래프 순회로 동작하기 때문에 그래프 탐색을 알고 있으면 이해하기 쉬운데

RootSpace부터 연결되어 갈 수 있는 모든 객체들까지 탐색하면서 마크를 해두면

연결되지 않은, 즉 참조되고 있지 않은 객체들은 마크가 되지 않기 때문에 삭제 대상이 될 것이다.

 

다음으로 Sweep 과정은 위에서 마크되지 않은 Unreachable 객체들을 Heap에서 제거한 후에

Compact 과정을 통해 삭제되지 않고 분산되어 떨어져 있는 객체들을 Heap의 시작주소로 모아 압축한다.

 

여기서 RootSpace는 Heap 영역을 참조하는 메서드 영역, 스태틱 변수, 스택, 네이티브 메서드 스택가 포함된다.

 

 

Heap 영역

GC이 대상인 Heap 영역은 Weak Generational Hypothesis를 이용해 설계되었는데

이는 대부분의 객체는 금방 Unreachable 상태가 되고, 오래된 객체에서 새로운 객체로의 참조는 적다는 점이다.

 

객체는 한 번 사용하기 위해 만들어지는 것이 대부분이고, 메모리에 오래 남아있지 않다는 것인데

Heap 영역은 이러한 관점에서 설계되 Young과 Old 영역으로 나누어 구성되었다.

 

 

Young Generation(Minor GC) / Old Generation(Major(Full) GC)

Young 영역은 새로 생성된 객체들이 할당 되고 대부분의 객체가 금방 Unreachable 상태가 되는 것처럼

대부분의 객체가 해당 영역에서 생성되었다가 사라지며, 이 영역에 대한 GC를 Minor GC라고 부른다.

 

Old 영역은 Young 영역에서 Reachable 상태를 오래 유지한 객체가 복사되는 영역으로

Young 영역보다 큰 영역이 할당된만큼 Garbage는 적게 발생하며, 해당 영역에 대한 GC를 Major GC라고 부른다.

 

대체로 수명이 짧은 객체들은 큰 공간을 사용하지 않기 때문에 수명이 긴 객체들이 저장되는 Old 영역이 더 크다.

 

 

Eden / Survivor0, 1

Young 영역은 Eden, Survivor0, Survivor1 세 가지 영역으로 다시 나누어진다.

 

Eden 영역은 new를 통해 새로 생성된 객체가 존재하며 이곳에서 정기적인 GC를 거친 후에

살아남은 객체들은 Survivor 영역으로 보내지게 된다.

 

Survivor 영역은 최소 1번 이상의 GC를 거친 후에 살아남은 객체가 존재하는 영역으로

0과 1 영역 중 하나는 무조건 비어 있어야 한다는 규칙이 있다.

 

힙 영역을 세분화하여 객체를 생존 기간별로 섬세하게 제어할 수 있어 GC를 정확하게 실행할 수 있게 된다.

 

 

Minor GC

Minor GC는 Old 영역에 비해 작은 Young 영역에서 객체를 찾고 제거하기 때문에 상대적으로 적은 시간이 소요된다.

 

우선 모든 객체들은 처음 생성될 때 Young 영역의 Eden 영역에 위치하게 되고, 해당 영역이 가득차면

Minor GC가 실행되어 Mark 과정을 통해 Reachable 객체를 Survivor 영역으로 이동시키고

Eden 영역의 Unreachable 객체의 메모리를 Sweep 하고 이러한 과정들은 반복한다.

 

이 때 살아남은 모든 객체들의 age를 1씩 증가시켜 GC에서 살아남은 횟수를 기록하게 되고

이 age 값이 임계값에 다다르면 Old 영역으로 이동하게 된다.

 

 

Major GC

Minor GC가 Eden 영역의 메모리가 부족할 때 실행되는 것처럼 Major GC도 Old 영역의 메모리가 부족하면 이루어진다.

 

객체의 age가 임계값에 이르러 Old 영역으로 이동하는 Promotion이 반복되서 진행되다

Old 영역의 메모리가 부족한 상황이 발생하면 Major GC가 실행되게 되고 참조되지 않는 객체들을 제거한다.

 

하지만 Old 영역에서의 GC는 큰 공간을 탐색하며 Unreachable 객체를 제거하기 때문에 오랜 시간이 소요되어

Stop The World 문제가 발생해 관련 없는 모든 쓰레드가 멈춘 후에 Mark and Sweep 작업이 수행된다.

 

 

'Java > Notion' 카테고리의 다른 글

자바의 버전별 특징  (0) 2024.03.04
JVM Warm Up  (0) 2024.03.04
JVM 뜯어보기 (컴파일, 인터프리팅, JIT, GC, 메모리)  (0) 2024.03.02
자바의 컴파일 과정  (0) 2023.10.25
BigInteger  (0) 2023.05.08
728x90

개요

우선 JVM 자체가 OS 위에서 자바 프로그램을 실행시켜 주는 역할을 수행해

운영체제에 독립적으로 실행할 수 있게 된다는 것은 당연히 알지만

JVM의 구성, 동작원리, 장단점 등에 대해 깊게 정리해보고자 한다.

 

컴파일 타입 환경에서의 자바 컴파일러

우리가 작성한 원시코드로 이루어진 java 파일은 JVM이 인식할 수 없기 때문에

자바 컴파일러가 java 파일을 자바 바이트코드로 이루어진 class 파일로 변환한다.

 

(java파일 > 어휘 분석 > 구문 분석 > 의미 분석 > 중간 코드 생성 후 최적화 = class 파일)

 

 

실제로 프로그램을 실행한 후에 java 파일들과 이름은 같지만 확장자가 다른

class 파일이 생성된 것을 확인할 수 있다.

 

cmd 창에서 직접 컴파일을 해봤으면 알 수도 있는데 그때 사용하는 javac 명령어는

JDK에 포함된 자바 컴파일러인 javac.exe를 의미한다.

 

이후 컴파일된 자바 바이트 코드를 JVM의 클래스 로더에 전달하게 된다.

 

런타임 환경에서의 JVM

클래스 로더는 동적로딩으로 필요한 클래스들을 로딩하고 링크하여

JVM의 메모리인 런타임 데이터 영역에 올리게 된다.

 

그 후 실행엔진이 메모리에 올라온 바이트 코드들을 명령어 단위로 가져와 실행하는데

이때, 인터프리터 방식과 JIT 컴파일러 방식을 사용한다.

 

클래스 로더의 세부 동작

클래스 로더는 로드 - 링크(검증 - 준비 - 분석) - 초기화 단계로 진행된다.

 

  1. 클래스 파일을 가져와 메모리에 로드
  2. 자바 언어 명세와 JVM 명세대로 구성되었는지 검증
  3. 필드, 메서드, 인터페이스 같이 클래스가 필요로 하는 메모리를 준비(할당)
  4. 클래스의 상수 풀 내 모든 심볼릭 레퍼런스를 다이렉트 레퍼런스로 변경(분석)
  5. 클래스 변수들을 적절한 값으로 초기화

 

자바 인터프리터

컴파일러는 클래스 파일 전체를 컴퓨터가 인식할 수 있는 이진 코드로 변환한다면

인터프리터는 런타임 시에 한 줄씩 읽으면서 이진 코드로 번역해서 실행한다.

 

추가적인 메모리를 사용하지 않고, 시스템의 이식성이 뛰어나며

코드가 수정되도 전체 코드를 다시 컴파일할 필요가 없다.

 

하지만 매번 인터프리팅을 거쳐야 하기 때문에 전체 실행 속도가 컴파일러에 비해 느리고

중간 코드로 변환되어 프로그램의 코드가 유출될 수 있다.

 

해당 방식을 기본으로 실행하며, 일정 기준이 넘어가면 JIT 컴파일 방식을 사용한다.

 

JIT 컴파일러

위의 인터프리터의 단점을 보완하기 위해 도입된 방식으로

바이트 코드 전체를 컴파일해 바이너리 코드로 변환해

매번 인터프리팅 하는 것이 아닌 바이너리 코드를 직접 실행한다.

 

한 번 컴파일만 완료되면 컴퓨터에서 빠르게 실행이 가능하고

기계어로 번역되어 코드 유출 위험이 적다.

 

하지만 코드가 수정된다면 컴파일을 다시 해야하고, 소스 파일 전체를 컴파일 해

용량이 크고, 모든 소스 파일을 번역하여 컴파일 시간이 비교적 느리며

목적 파일 생성을 위해 추가적인 메모리를 사용한다.

 

JVM의 구성

클래스 로더
메모리 영역 = [메서드 영역, 힙 영역, JVM 스택 영역, PC 레지스터, 네이티브 메서드 스택 영역]
실행 엔진 = [인터프리터, JIT 컴파일러, GC]
JNI
네이티브 메서드 라이브러리

 

JVM은 크게 클래스 로더와 메모리, 실행 엔진으로 구성되어 있으며

클래스 로더와 인터프리터, JIT 컴파일러는 위에서 살펴봤으니 나머지만 알아보겠다.

 

GC(Garbage Collection)

Heap 메모리 영역에서 더이상 사용하지 않는 메모리를 자동으로 회수해

C언어처럼 개발자가 메모리를 직접 관리할 필요가 없다.

 

일반적으로는 자동으로 실행되지만 실행되는 시간이 따로 정해져 있지는 않고

System.gc() 메서드를 사용해 수동으로도 가능하지만 실행이 보장되지도 않는다.

 

GC는 해당 게시글에 정리하기에는 내용이 적지 않은 편이라

추후에 따로 정리하겠다.

 

메모리 영역(런타임 데이터 영역)

자바 애플리케이션을 실행할 때 사용되는 데이터들이 적재되는 영역이다.

 

  • 메서드 영역
    • 클래스와 인터페이스의 런타임 상수 풀, 메서드와 필드, 클래스 변수, 메서드 바이트 코드 등이 적재
    • JVM 시작시 생성되어 프로그램 종료 시까지 존재
    • 런타임 상수 풀은 클래스와 인터페이스의 상수, 메서드와 필드에 대한 모든 레퍼런스를 저장하는 곳
    • JVM은 런타임 상수 풀에서 주소를 찾아 참조
    • GC 대상이 아니지만 명시적으로 null 선언시 GC 대상이 될 수 있음
    • 모든 쓰레드가 공유
  • 힙 영역
    • 프로그램 상에서 런타임 시 동적으로 할당해 데이터를 저장하는 영역
    • New 연산자로 생성된 인스턴스, 배열 같은 참조형 타입 저장
    • JVM이 직접 관리
    • 객체가 더 이상 사용되지 않거나, 명시적으로 null 선언시 GC 대상
    • 객체와 배열이 저장된 곳일뿐 참조 주소는 스택 영역에 있다.
    • 모든 쓰레드가 공유
  • 스택 영역
    • 선입후출 구조
    • 메서드 호출 시 생성되는 스레드 수행정보를 기록하는 Frame 저장
    • 메서드 정보, 지역변수, 매개변수, 연산 중 발생하는 임시 데이터 저장
    • 스택 프레임(중괄호 블록)이나 메서드가 끝날 때 저장된 데이터들이 사라진다.
    • 각 쓰레드 마다 생성되는 개별 영역
  • PC 레지스터
    • 현재 실행 중인 JVM 명령어 주소를 가지고 있다.
    • CPU 명령어(instruction)를 수행한다
    • JVM의 리소스를 이용해 연산을 수행해야 하기 때문에
    • CPU가 직접 연산을 수행하는 것이 아닌 현재 작업 내용을 CPU에게 연산으로 제공
    • 이를 위한 버퍼 공간이라고 볼 수 있다.
    • 각 쓰레드 마다 생성되는 개별 영역
  • 네이티브 메서드 스택 영역
    • 자바 외 언어로 작성된 네이티브 코드를 위한 메모리 (C/C++ 등)
    • 네이티브 메서드의 매개변수, 지역변수 등을 바이트 코드로 저장
    • 네이티브 인터페이스 호출 및 종료 시 생성
    • 각 쓰레드 마다 생성되는 개별 영역

 

JNI (Java Native Interface)

다른 언어로 만들어진 애플리케이션과 상호 작용할 수 있는 인터페이스를 제공한다

 

네이티브 메서드 라이브러리

다른 언어로 작성된 라이브러리를 칭하며 필요한 경우 JNI가 해당 라이브러리를 로딩한다.

'Java > Notion' 카테고리의 다른 글

JVM Warm Up  (0) 2024.03.04
Garbage Collection 파헤치기  (0) 2024.03.03
자바의 컴파일 과정  (0) 2023.10.25
BigInteger  (0) 2023.05.08
Optional<T>  (0) 2023.05.03
728x90

자바의 컴파일 과정

자바는 JVM을 통해 OS에 독립적으로 실행시킬 수 있는데

어떻게 이런 일이 가능한지 알기 위해 자바의 컴파일 과정을 살펴보겠습니다.

 

public class Main {
	public static void main(String[] args) {
    	System.out.println("Hello");
    }
}

 

우리는 보통 위와 같이 ".java" 확장자를 가진 파일에 소스 코드를 작성하고

인텔리제이나 이클립스 같은 IDE로 손쉽게 버튼을 클릭해 실행하거나

"javac"와 "java" 명령어를 통해서 실행할 수 있습니다.

 

컴파일 타임 환경

 

 

이 과정에서 소스 코드를 실행하기 위해 컴파일 하는 과정을 거치게 되는데

위 이미지와 같이 ".java" 파일이 컴파일러를 거쳐 ".class"파일이 생성되게 된다.

 

여기서 생성된 자바 바이트 코드는 아직은 컴퓨터가 읽을 수 없는 코드

JVM만 이해할 수 있는 코드입니다.

(프로젝트의 최상위에 존재하는 out 폴더에서 class 파일을 확인할 수 있다)

 

런타임 환경

 

 

바이트 코드는 JVM만 이해할 수 있는 언어이므로 이를 JVM의 클래스 로더에 전달하고

클래스 로더가 동적 로딩을 통해 필요한 클래스들만 로딩 및 링크하여

JVM의 메모리(런타임 데이터 영역)에 올립니다.

 

이 과정에서 클래스 로더의 세부 동작은 아래와 같습니다.

  1. 로드 : 클래스 파일을 가져와 JVM의 메모리에 로드
  2. 검증 : 자바 언어와 JVM의 명세에 명시된 대로 구성된지 검증
  3. 준비 : 클래스가 필요로 하는 메모리를 할당 (필드, 메서드, 인터페이스 등)
  4. 분석 : 클래스의 상수 풀 내 모든 심볼릭 레퍼런스를 다이렉트 레퍼런스로 변경
  5. 초기화 : 클래스 변수들을 적절한 값으로 초기화 (정적 필드)

 

이 후에 실행 엔진이 JVM 메모리에 올라온 바이트 코드들을

인터프리터 혹은 JIT 컴파일러 방식으로 명령어를 가져와서 실행합니다.

 

인터프리터는 바이트 코드 명령어를 하나씩 읽어서 해석하고 실행하여

각각의 실행은 빠르나 전체적인 실행 속도가 느리다는 단점이 있습니다.

 

JIT 컴파일러 방식은 바이트 코드 전체를 컴파일 하여 바이너리 코드로 변경한 후에

항상 바이너리 코드로 직접 실행하는 방식으로

매번 하나씩 인터프리팅 하지 않아 전체적인 실행 속도가 빠릅니다.

 

Reference

https://steady-snail.tistory.com/67

 

[JAVA] JVM 동작원리 및 기본개념

JAVA라는 언어를 통해 코딩을 하고 있는 사람으로서 JAVA의 간단한 탄생배경 그리고 JAVA의 시작과 끝이라고 할 수 있는 JVM을 한 번 짚고넘어가려고 해요 우선 JAVA의 탄생배경을 좀 알고가면 이해하

steady-snail.tistory.com

https://aljjabaegi.tistory.com/387

 

알기쉽게 정리한 JAVA의 컴파일과정 및 JVM 메모리 구조, JVM GC

알기쉽게 정리한 JAVA의 컴파일과정 및 JVM 메모리 구조, JVM GC 자바 개발자들이 간과 하기 쉬운 JAVA의 메모리 구조에 대해 포스팅 해보려고 합니다. 이와 관련하여 JAVA의 컴파일 과정과 Garbage Collect

aljjabaegi.tistory.com

 

'Java > Notion' 카테고리의 다른 글

Garbage Collection 파헤치기  (0) 2024.03.03
JVM 뜯어보기 (컴파일, 인터프리팅, JIT, GC, 메모리)  (0) 2024.03.02
BigInteger  (0) 2023.05.08
Optional<T>  (0) 2023.05.03
Stream  (1) 2023.05.03
728x90

기본 타입인 int, long 타입으로 표현할 수 없는 크기의 값을 처리할 때 사용

표현할 수 있는 범위가 크기 때문에 정밀한 작업을 할 때 사용

 

메서드

gcd()

두 값의 최대 공약수를 반환

add()

덧셈

substract()

뺄셈

multiply()

곱셈

divide()

나눗셈

valueOf()

문자열 혹은 숫자를 BigInteger 타입으로 변환

 

비교연산

직접적인 비교연산자 사용은 불가능

equals, compareTo 등을 사용해서 비교

 

변환

Number 클래스를 상속 받기 때문에

int, long, float, double, byte, short로 Value메서드를 사용해 변환 가능

'Java > Notion' 카테고리의 다른 글

JVM 뜯어보기 (컴파일, 인터프리팅, JIT, GC, 메모리)  (0) 2024.03.02
자바의 컴파일 과정  (0) 2023.10.25
Optional<T>  (0) 2023.05.03
Stream  (1) 2023.05.03
Lambda  (0) 2023.05.03
728x90

T 타입 객체의 Wrapper 클래스

null을 포함한 모든 종류의 객체를 저장하기 때문에 null을 간접적으로 다룰 수 있음

NullPorinterException 발생을 예방할 수 있음

null 대신 빈 Optional<T> 객체를 사용하는 것이 좋음

public final class Optional<T> {
	private final T value;
    // null을 포함한 모든 종류의 객체를 저장가능
}

Optional<T> 객체 생성

Optional<String> optVal = Optional.empty(); // 빈 객체로 초기화

Optional<String> optVal - Optional.of(str);
Optional<String> optVal - Optional.of("abc");
Optional<String> optVal - Optional.of(null); // 오류. 불가능
Optional<String> optVal - Optional.ofNullable(null); // 가능

Optional<T> 객체 값 가져오기

String str = optVal.get(); // null이면 예외가 발생하여 잘 사용하지 않음
String str = optVal.orElse(""); // 저장된 값이 null이면 ""반환. 많이 사용
String str = optVal.orElseGet(람다식,메소드참조); // 많이 사용
String str = optVal.or.ElseThrow(NullPointerException::new); // null이면 예외 발생

isPresent() // Optional 객체의 값이 null이면 false, 아니면 true
ifPresent(작업) // null이 아닐 때만 작업 수행, null이면 아무 일도 안함

 

'Java > Notion' 카테고리의 다른 글

자바의 컴파일 과정  (0) 2023.10.25
BigInteger  (0) 2023.05.08
Stream  (1) 2023.05.03
Lambda  (0) 2023.05.03
Thread  (0) 2023.05.02
728x90

다양한 데이터 소스(컬렉션, 배열 등)를 표준화된 방법으로 다루기 위한 것

  1. 스트림 생성
  2. 중간연산 : 연산결과가 스트림인 연산으로 반복연산 가능
  3. 최종연산 : 연산결과가 스트림이 아닌 연산으로 스트림의 요소를 소모하기 때문에 단 한번만 적용 가능

스트림의 특징

스트림은 데이터 소스(원본)로부터 데이터를 읽기만하고 변경은 하지 않음

일회용이라 다시 사용하려면 스트림을 새로 생성해야 함

최종 연산 전까지 중간 연산이 수행되지 않음

작업을 내부 반복으로 처리함 (성능은 떨어지지만 코드가 간결해짐)

작업을 병렬로 처리하는 병렬스트림으로 멀티쓰레드와 비슷

오토박싱과 언박싱의 비효율을 처리하기 위해 기본형 스트림 지원

스트림 생성

// 컬렉션 >> 스트림
List<Integer> list = Arrays.asList(1,2,4,5,3,6);
Stream<Integer> intStream - list.stream();

// 배열 >> 스트림
Stream<String> strStream = Stream.of("a", "b", "c"); // 가변 인자라 길이가 무한함
Stream<String> strStream = Stream.of(new String[]{"a", "b", "c"});
Stream<String> strStream = Arrays.stream(new String[]{"a", "b", "c"});
Stream<String> strStream = Arrays.stream(new String[]{"a", "b", "c"}, 0, 3); // 필요한 만큼만

// 기본형 배열 >> 스트림
IntStream IntStream.of(int 요소들);
IntStream IntStream.of(int[]);
IntStream Arrays.stream(int[]);

// 임의의 수 >> 스트림
IntStream intStream = new Random().ints(); // 무한 스트림
intStream.limit(5).forEach(System.out::println); // 무한 스트림에 제한을 줌
IntStream intStream = new Random().ints(5); // 개수를 지정한 유한 스트림

// 특정 범위 정수 >> 스트림
IntStream intStream = IntStream.range(시작범위, 끝범위(포함안됨));
IntStream intStream = IntStream.rangeClosed(시작범위, 끝범위(포함));

// 람다식(iterate, generate) >> 스트림 (무한 스트림)
// iterate는 이전 요소를 seed로 해서 다음 요소 계산
static <T> Stream<T> iterate(T seed(초기값), UnaryOperator<T> f(람다식)) // 이전 요소에 종속적
// generate는 seed를 사용하지 않음
static <T> Stream<T> generate(Supplier<T> s(람다식)) // 이전 요소에 독립적

// 파일 >> 스트림
Stream<Path> f = Files.list(Path dir(폴더경로) //지정 폴더의 파일들의 경로
Stream<String> f = Files.lines(Path path) // lines 메소드는 파일 내용을 라인 단위로 읽어옴
Stream<String> f = Files.lines(Path path, Charset cs)
Stream<String> f = lines() // BufferedReader 클래스의 메소드

// 빈 스트림 생성
Stream emptyStream = Stream.empty(); // empty 메소드는 빈 스트림을 생성해서 반환

// 스트림 종류 변경
sequential() // 기본값
parallel() // 병렬스트림

스트림 중간 연산

// 제거 및 제외
distinct() // 중복을 제거
filter(조건식) // 조건에 해당하지 않는 요소 제외
limit(long maxSize) // 지정 크기까지만 스트림의 일부를 잘라냄
skip(long n) // 앞에서 부터 스트림의 n개를 건너 뜀

//정렬
sorted() // 기본 정렬
sorted(Comparator.reverseOrder()) // 역순 정렬
sorted(String.CASE_INSENSITIVE_ORDER) // 대소문자 구분없는 기본 정렬
sorted(String.CASE_INSENSITIVE_ORDER.reversed()) // 대소문자 구분없는 역순 정렬
sorted(Comparator.comparing(String::length)) // 길이순 정렬
sorted(Comparator.comparingint(String::length)) // 오토박싱 없음
sorted(Comparator.comparing(String::length).reversed()) // 길이순 역순 정렬
sorted(람다식/메소드참조)

// Comparator.comparing() : 정렬 기준 제공
studentStream.sorted(Comparator.comparing(Student::getBan)) // 학생 객체의 학생들을 반별로 정렬
// thenComparing : 추가 정렬 기준 제공
.thenComparing(Student::getName) // 반별로 정렬할 때 이름별로 정렬도 하고 싶은 경우

peek() // 스트림의 요소를 소비하지 않고 중간 결과를 볼 수 있음

// 스트림의 요소 변환
// 예시
// 파일의 정보가 담긴 파일 스트림을
Stream<File> fileStream = Stream.of(new File(파일1), new File(파일2), new File(파일3))
// 파일의 이름만 추출해서 파일명 스트림으로 변환
Stream<String> filenameStream = fileStream.map(File::getNaem);
map() //

// 스트림의 스트림을 스트림으로 변환 (간장공장공장장...)
// {"abc", "def", "jkl" }, {"ABC", "GHI", "JKL"} 스트링 배열 스트림을 map을 사용해서 변환하면
// Stream<Stream<String>> 스트림의 스트림이 만들어짐
// Stream<String> 으로 만들기 위해 사용하는 것이 flatMap
Stream<String> strStream = strArrStream.flatMap(Arrays::stream);

스트림 최종 연산

// 스트림의 모든 요소에 지정된 작업을 수행
forEach() // 병렬스트림인 경우 순서가 보장되지 않음
forEachOrdered() // 병렬스트림인 경우도 순서가 보장

// 조건 검사 
allMatch(조건식) // 모든 요소가 조건을 만족시켜야 true
anyMatch(조건식) // 한 요소라도 조건을 만족시키면 true
noneMatch(조건식) // 모든 요소가 조건을 만족시키지 않아야 true

// filter + findAny(), findFirst()
findAny() // 병렬 스트림에서 사용하며 조건에 맞는 아무 요소나 하나를 반환
findFirst() // 순차 스트림에서 사용하며 조건에 맞는 첫 번째 요소를 반환

// 스트림의 요소를 하나씩 줄여가며 누적연산 수행
reduce(
	identity(초기값), // 많이 사용
    accumulator(수행할 연산), // 많이 사용
    combiner((병렬 처리된 결과를 합치는데 사용할 연산-병렬스트림))
    )

// 스트림의 그룹화 연산
// collect에 필요한 메서드를 정의해놓은 Collector 인터페이스를 매개변수로 받음
// Collectors 클래스가 인터페이스를 이미 다 구현해놨기 때문에 직접 구현할 필요 없음
collect(Collector collector) 

// Collectors 클래스
toList(), toSet(), toMap, toCollection() // 스트림을 컬렉션으로 변환
toArray() // 스트림을 배열로 변환해주지만 타입을 지정 안해주면 기본으로 오브젝트 타입
counting(), summingInt(), maxBy(), minBy() // 그룹별로 통계를 낼 수 있음
reducing() // 그룹별 reduce
joining() // 문자열 스트림의 요소를 모두 연결

count()
max()
min()

스트림 그룹화 및 분할

// 스트림 2분할
partitioningBy()

// 스트림 n분할
groupingBy()

 

'Java > Notion' 카테고리의 다른 글

BigInteger  (0) 2023.05.08
Optional<T>  (0) 2023.05.03
Lambda  (0) 2023.05.03
Thread  (0) 2023.05.02
애너테이션  (0) 2023.05.02

+ Recent posts