728x90

문제

 

 

접근 방식

가장 많은 양의 포도주를 마실 수 있는 경우를 찾아야 하는데

두 번째 조건을 보면 연속으로 마실 수 있는 포도주는 두 잔까지고

이는 1,2번째 잔을 마셨다면 3번째 잔은 건너뛰고

4,5,6,...n번째 포도주 중 하나를 먹어야 한다는 말이다.

 

거꾸로 생각해 보면 두 잔을 연속으로 마실 때는 이전 잔을 무조건 마셨다는 것이 되고

한 잔만 마셨을 때는 바로 이전 잔을 제외한 이전의 어떠한 잔들 중 하나 이상은 마신 것이 된다.

 

만약 n번째 잔을 마실 때, 해당 잔이 연속으로 두 번째 마시고 있는 잔이라면

(n번 잔의 양 + n-1번 잔을 첫 번째로 마신 경우의 최댓값) 이 해당 경우의 최댓값이 되고

해당 잔이 연속이 아닌 첫 번째로 마시고 있는 잔이라면

n-2 이하 번의 잔의 최댓값 + n번 잔의 양이 최댓값이 된다.

 

점화식을 정리하면 다음과 같다.

dp[n][한 잔을 연속으로 마신 경우] = dp[n-2][누적 최댓값] + n번 잔의 양
dp[n][두 잔을 연속으로 마신 경우] = dp[n-1][한 잔을 연속으로 마신 경우] + n번 잔의 양
dp[n][누적 최댓값] = max(dp[n-1][누적 최댓값], dp[n][한 잔을 연속으로 마신 경우], dp[n][두 잔을 연속으로 마신 경우])

 

 

풀이

public class Main {

    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));

        int n = Integer.parseInt(br.readLine());

        int[] cups = new int[n + 1];
        int[][] dp = new int[n + 1][3];

        for (int i = 1; i <= n; i++) {
        	cups[i] = Integer.parseInt(br.readLine());
        }

        dp[1][0] = cups[1];
        dp[1][2] = cups[1];

        for (int i = 2; i <= n; i++) {
            dp[i][0] = dp[i - 2][2] + cups[i];
            dp[i][1] = dp[i - 1][0] + cups[i];
            dp[i][2] = Math.max(dp[i - 1][2], Math.max(dp[i][0], dp[i][1]));
        }

        System.out.println(dp[n][2]);
    }
}

 

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

[백준] 2293번 : 동전 1  (0) 2024.03.09
[백준] 11052번 : 카드 구매하기  (0) 2024.03.08
[백준] 1904번 : 01타일  (0) 2024.03.07
[프로그래머스] 더 맵게  (0) 2024.03.05
[백준] 10994번 : 별 찍기 - 19  (0) 2024.02.27
728x90

문제

 

 

접근 방식

한 자리의 경우 : 1
두 자리의 경우 : 11, 00
세 자리의 경우 : 111, 100, 001
네 자리의 경우 : 1111, 1100, 1001, 0011, 0000
다섯 자리의 경우 : 11111, 11100, 11001, 10011, 10000, 00111, 00100, 00001

 

각 자릿수 별로 경우의 수를 적어보면 위와 같다.

 

우리가 사용할 수 있는 숫자는 한 자리인 1과 두 자리인 00 두 가지만 있는데

만약 여섯 자리의 수를 만들어야 한다 가정하면

첫자리에 1이 오면 그 뒤에는 다섯 자리의 수가 올 수 있을 테니

기존에 구해둔 다섯 자리의 경우만큼 만들 수 있을 것이고

첫자리에 00이 오면 그 뒤에는 네 자리의 수가 올 수 있으니

기존에 구해둔 네 자리의 경우만큼 만들 수 있을 것이다.

 

결국 1로 시작하는 경우와 00으로 시작하는 경우를 합쳐주면

n자리의 수를 만들 수 있는 경우의 수를 구할 수 있다.

 

점화식을 정리하면

첫자리에 1이 오는 경우는 (n-1)자리의 경우의 수가 되고

첫 자리에 00이 오는 경우는 (n-2)자리의 경우의 수가 되니

f(n) = f(n-1) + f(n-2) 가 된다.

 

풀이

public class Main {

    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));

        int n = Integer.parseInt(br.readLine());

        int[] arr = new int[1000001];

        arr[1] = 1;
        arr[2] = 2;

        for (int i = 3; i <= n; i++) arr[i] = (arr[i - 1] + arr[i - 2]) % 15746;

        System.out.println(arr[n]);
    }
}

 

728x90

문제

 

 

접근 방식

가장 낮은 값과 (다음으로 낮은 값 * 2)을 더한 값을 다시 포함하여

모든 수가 K 이상이 되게 만들어야 한다.

 

즉, 가장 낮은 값 두 개를 뺀 후에 섞고 다시 섞은 것을 추가해야 하고

이 때, 항상 정렬된 상태를 유지해야 하는게 중요하다.

 

섞은 것을 추가할 때마다 정렬을 하기에는 시간 복잡도가 비효율적이기 때문에

값이 추가되어도 항상 정렬된 상태를 유지할 수 있는 우선순위 큐를 사용하면 쉽다.

 

처음에는 우선순위 큐에 모든 값들을 넣어주고

큐에서 가장 작은 값을 두 개씩 빼가며 섞어주고 다시 넣어주다가

꺼낸 값이 K 이상이라면 이후의 값들도 당연히 K 이상이기 때문에

현재까지의 카운트를 리턴해주면 되고

값을 하나 꺼냈는데 더 이상 남은 것이 없다면 더 이상 섞을 것이 없기 때문에

모든 값을 K 이상으로 만들 수 없다는 것이 되어 -1을 출력해준다.

 

 

풀이

class Solution {
    public int solution(int[] scoville, int K) {
        PriorityQueue<Integer> q = new PriorityQueue<>();
        
        for (int s : scoville) q.offer(s);
        
        int cnt = 0;
        
        while(!q.isEmpty()) {
            int first = q.poll();
            
            if(first >= K) break;
            if(q.isEmpty()) return -1;
            
            cnt++;
            
            q.offer(first + (q.poll() * 2));
        }
        
        return cnt;
    }
}

 

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

[백준] 2156번 : 포도주 시식  (0) 2024.03.08
[백준] 1904번 : 01타일  (0) 2024.03.07
[백준] 10994번 : 별 찍기 - 19  (0) 2024.02.27
[백준] 9184번 : 신나는 함수 실행  (0) 2024.02.23
[백준] 4779번 : 칸토어 집합  (0) 2024.02.22
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

문제

 

 

접근 방식

별 찍기 문제들은 재귀 처음 배울 때 너무 싫어했던 기억이 있어서

풀기 싫었는데 스트릭 유지할겸 오랜만에 재귀 문제를 풀어보았다.

 

정사각형이 점점 작아지는 형태로

예를 들면 가장 밖의 정사각형이 13 * 13이라면

그 안의 사각형은 상하좌우 한 칸 거리를 두고 9 * 9

그 안의 사각형도 상하좌우 한 칸 거리를 두고 5 * 5

마지막은 1 * 1이 된다.

 

규칙을 찾아보면 사각형의 가로세로 길이가 4씩 줄어드는 것을 알 수 있는데

재귀를 호출할 때마다 x축과 y축을 4씩 늘리거나 줄여주면서

2차원 문자 배열에 별을 저장한 후에 모두 끝나면 배열을 출력해주면 된다.

 

 

풀이

public class Main {

	private static char[][] stars;

	public static void main(String[] args) throws IOException {
		BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
		StringBuilder sb = new StringBuilder();

		int n = Integer.parseInt(br.readLine());

		stars = new char[1 + (n - 1) * 4][1 + (n - 1) * 4];

		star(0, 1 + (n - 1) * 4);

		for (char[] star : stars) {
			sb.append(star).append("\n");
		}

		System.out.println(sb);
	}

	private static void star(int x, int y) {

		if (x >= y) return;

		for (int i = x; i < y; i++) {
			if (i == x || i == y - 1) {
				for (int j = x; j < y; j++) stars[i][j] = '*';
			} else {
				stars[i][x] = '*';
				for (int j = x + 1; j < y - 1; j++) stars[i][j] = ' ';
				stars[i][y - 1] = '*';
			}
		}

		star(x + 2, y - 2);
	}
}

 

+ Recent posts