DAY 1

2024. 2. 16. 18:08

컴파일 과정

=> 자바 컴파일러(javac)를 사용하여 소스 코드를 컴파일한다. javac는 JDK(Java Development Kit)에 포함되어 있다. javac 명령어를 사용하여 컴파일 수행 가능. 컴파일러는 소스 코드를 바이트 코드로 변환하는 역할을 한다. 컴파일러가 소스 코드를 컴파일하면, 해당 소스 코드에 대한 바이트 코드(.class) 파일이 생성된다. 바이트 코드느 JVM에서 실행되는 중간 언어로, 플랫폼에 독립적이다. 생성된 바이트 코드는 JVM에서 해석되어 실행된다. JVM은 운영체제에 종속적이며, 각 운영체제에 맞게 JVM이 설치되어야 한다. JVM은 바이트 코드를 해석하고 실행하여 프로그램이 동작하도록 한다.

 

컴파일된 바이트 코드는 플랫폼에 독립적이다. 어떤 운영체제에서든 사용가능하다. 하지만 바이트 코드는 JVM에서 실행되는데, JVM은 각 운영체제에 맞게 설치되어야 한다.

컴파일된 바이트 코드는 JVM 위에서 실행되기 때문에, 자바의 크로스 플랫폼 특성을 제공한다. 즉, 한 번 컴파일된 프로그램은 어떤 운영체제에서든 실행할 수 있다.

 

Java Virtual Machine

=> 자바 프로그램을 실행하기 위한 가상 컴퓨터이다. JVM은 운영체제에 독립적이며, 자바 프로그램의 이식성과 보안성을 보장하는 역할을 수행한다.

1. 자바 바이트 코드 실행 : JVM은 자바 컴파일러에 의해 생성된 바이트 코드를 해석하고 실행한다. 바이트 코드는 JVM이 이해할 수 있는 중간 언어로, JVM은 이를 자신의 기계어로 변환하여 실행한다.

2. 메모리 관리 : JVM은 자바 프로그램이 실행되는 동안 메모리를 관리한다. 이는 자동적으로 이루어지며, GC라는 매커니즘을 사용하여 프로그램이 동적으로 할당한 메모리를 관리한다.

3. 자바 클래스 로딩 : JVM은 필요한 클래스 파일을 동적으로 로딩한다. 클래스 로더는 클래스 파일을 찾아서 JVM 내부로 로드하고, 필요한 시점에 클래스를 초기화한다.

 

* 클래스 로딩은 JVM이 클래스를 사용하기 위해 해당 클래스의 바이트 코드를 메모리로 로드하는 과정을 말한다. 클래스 로딩은 클래스의 인스턴스 생성, 정적 필드 또는 메서드에 접근 등의 상황에서 발생한다.

클래스 로딩 세 단계

1). 로딩 : 클래스 로더는 클래스 파일을 찾고, 해당 클래스의 바이트 코드를 읽어와 JVM 내부의 메모리에 로드한다. 이때, 로드된 클래스는 메모리 상에 클래스 정보를 저장하게 된다.

2). 링크 : - 검증 : 로드된 클래스의 바이트 코드가 유효하고 안전한지 확인한다. 클래스의 구조적인 정합성, 상속 관계 등을 검사한다.

- 준비 : 클래스의 정적 필드를 위한 메모리 공간을 할당하고, 기본값으로 초기화한다.

- 해석 : 클래스가 다른 클래스나 메서드를 참조하는 경우, 해당 참조를 실제 메모리 상의 주소로 해석한다.

3). 초기화 : 클래스의 정벅 변수들을 선언된 값으로 초기화하고, 정적 초기화 블록이 있다면 실행한다. 필요한 경우에만 발생.

JVM이 클래스가 처음 사용되는 시점에 동적으로 발생하며, 로딩된 클래스는 JVM 내에서 공유되어 여러 인스턴스에서 사용될 수 있다. 동적 로딩 기능도 제공한다.

 

4. JIT 컴파일러 : JVM은 Just-In-Time 컴파일러를 통해 바이트 코드를 실제 기계어로 변환하여 실행 성능을 향상시킨다. JIT 컴파일러는 실행 시점에 바이트 코드를 분석하여 빈번하게 호출되는 부분을 동적으로 기계어로 컴파일한다.

 

* JVM은 프로그램 실행 중에 실행 패턴을 분석하고, 자주 사용되는 메서드나 루프 등의 코드 블록을 식별한다. 이러한 코드 블록은 JIT 컴파일러에 의해 기계어로 컴파일되어 최적화된 실행을 제공한다.

하지만 프로그램의 시작 부분이나 드물게 실행되는 코드는 JIT 컴파일러에 의해 기계어로 변환되지 않을 수 있다. 이러한 코드는 인터프리터에 의해 바이트 코드로 해석되고 실행될 수 있다.

 

** 컴파일러 방식 VS 인터프리터 방식

 - 컴파일러 방식은 반드시 인터프리터가 필요하다(프로그램 시작 부분을 실행해야하기 때문)

 

5. 예외 처리 : JVM은 자바 프로그램에서 발생하는 예외(Exception)를 처리한다. 예외 처리는 프로그램의 안정성과 오류 복구 기능을 제공한다.

6. 멀티스레딩 지원 : JVM은 멀티스레드 프로그래밍을 지원하며, 스레드 스케쥴링과 동기화 등의 기능을 제공한다. 이를 통해 여러 작업을 동시에 처리하고 병렬성을 활용할 수 있다.

7. 보안 관리 : 클래스 로더를 통해 로드되는 클래스들에 대해 보안 검사를 수행하고, 악성 코드의 실행을 방지한다.

8. 플랫폼 독립성 : 한 번 작성된 자바 프로그램은 어떤 운영체제에서든 실행할 수 있다. JVM은 플랫폼에 특화된 구현체로 제공되며, 각 운영체제에 맞게 설치되어야 한다.

 

 

클래스 로더는 동적 로딩을 통해 필요한 클래스들을 로딩 및 링크하여 Runtime Data Area(실질적인 메모리를 할당 받아 관리하는 영역)에 올린다.

Runtime Data Area 에 로딩된 바이트 코드는 Execution Engine을 통해 해석된다.

 

* JVM의 구조

 

 

클래스 로더는 JVM 내로 클래스 파일(*.class)을 동적으로 로드하고, 링크를 통해 배치하는 작업을 수행하는 모듈.

즉, 로드된 바이트코드들을 엮어서 JVM의 메모리 영역인 Runtime Data Area에 배치한다.

 

* 실행 엔진(Execution Engine)

실행 엔진은 클래스 로더를 통해 런타임 데이터 영역에 배치된 바이트 코드를 명령어 단위로 읽어서 실행한다.

 

런타임 데이터 영역은 JVM의 메모리 영역으로, 자바 애플리케이션을 실행할 때 사용되는 데이터들을 적재하는 영역

 

 

Call By Value / Call By Reference

 

1. Call By Value(값에 의한 호출)

- 함수 호출 시에 인자로 전달되는 값의 복사본이 함수의 매개변수로 전달된다.

- 함수 내에서 매개변수의 값을 변경해도 원본 변수에는 영향을 주지 않는다.

2. Call By Reference(참조에 의한 호출)

- 함수 호출 시에 인자로 전달되는 변수의 참조(메모리 주소)가 함수의 매개변수로 전달된다.

- 함수 내에서 매개변수를 통해 원본 변수를 직접 조작할 수 있다.

 

자바에서는 Call By Value 만을 지원한다. 기본 데이터 타입의 경워 값이 복사되어 전달되고, 객체의 경우 객체의 참조(주소 값)가 복사되어 전달된다. 따라서 함수 내에서 매개변수 값을 변경하더라도 원본 변수에는 영향을 주지 않는다. 그러나 객체의 경우 객체 자체는 Call By Value로 전달되지만 객체의 내부 상태(멤버 변수)는 변경될 수 있다.

 

 

Thread

동시에 실행되는 코드의 단위로, 여러 작업을 동시에 처리하거나 병렬로 실행할 수 있게 해주는 기능.

Thread를 사용하여 동시성(concurrency)을 구현할 수 있으며, 다중 스레드 프로그래밍을 통해 응답성(Responsivenes)을 향상시킬 수 있다.

1. 스레드 개념

- 스레드는 프로세스 내에서 실행되는 작은 실행 단위이다. 하나의 프로세스는 여러 개의 스레드를 가질 수 있다.

- 각 스레드는 독립적으로 실행되며, 각각의 스레드는 자신만의 스택(Stack)을 가지고 있다.

- 스레드는 프로세스의 자원을 공유하면서 동시에 실행될 수 있다.

2. 스레드 생성과 실행

- 자바에서 스레드를 생성하고 실행하기 위해 Thread 클래스를 사용한다. Thread 클래스를 상속받거나, Runnable 인터페이스를 구현하는 방식으로 스레드를 생성할 수 있다.

- Thread 클래스를 상속받은 경우, run() 메서드를 오버라이딩하여 스레드가 실행될 코드를 작성한다.

- Runnable 인터페이스를 구현한 경우, run() 메서드를 구현하고, Thread 객체를 생성하여 Runnable 객체를 매개변수로 전달한다.

3. 스레드 상태와 제어

- 스레드는 다양한 상태를 가지며, 주요한 상태로는 NEW, RUNNABLE, BLOCKED, WAITING, TIMED_WAITING, TERMINATED 등이 있다.

- 스레드의 실행을 제어하기 위해 sleep(), yield(), join(), interrupt() 등의 메서드를 사용할 수 있다.

4. 동기화와 스레드 간 통신

- 여러 스레드가 공유 자원에 동시에 접근할 때, 데이터의 일관성을 유지하기 위해 동기화가 필요하다.

- 동기화를 위해 synchronized 키워드를 사용하여 Critical Section을 설정할 수 있다.

5. 스레드 풀

- 미리 생성된 스레드들을 관리하고 재사용하는 기법

- java.util.cuncurrent 패키지에서 Executor와 ExecutorService 인터페이스를 통해 스레드 풀을 구현할 수 있다.

- 스레드의 생성과 종료에 따른 오버헤드를 줄이고, 작업 처리량을 효율적으로 관리할 수 있다.

 

=> 스레드는 자바에서 다중 스레드 프로그래밍을 구현하는 핵심 요소이다. 적절히 활용하면 병렬 처리와 응답성 향상을 이룰 수 있다. 그러나 스레드를 잘못 사용하면 상태 동기화 문제나 데드락 등의 문제가 발생할 수 있으므로 주의가 필요하다.

 

Casting

변수나 표현식의 데이터 타입을 다른 데이터 타입으로 변환하는 작업.

자동 형변환(Automatic Casting)과 강제 형변환(Explicit Casting)

1. 자동 형 변환 : 크기가 작은 데이터 타입이 큰 데이터 타입으로 자동으로 변환되는 것을 말한다. 이는 데이터의 손실이 발생하지 않거나 최소화되는 경우에만 가능하다. 예를 들어 int 타입의 변수를 double 변수에 할당하면 자동으로 형 변환이 이루어진다.

2. 강제 형 변환 : 큰 데이터 타입을 작은 데이터 타입으로 변환하는 것. 이 경우 데이터의 손실이 발생할 수 있으므로 명시적으로 형 변환을 해주어야 한다.

 

또한 상속 관계에 있는 클래스 간의 형 변환도 가능하다. = 참조형 캐스팅 = 사용할 수 있는 멤버의 갯수를 조절하는 것.

업캐스팅(Upcasting)과 다운캐스팅(Downcasting)

업캐스팅은 자식 클래스의 인스턴스를 부모 클래스 타입으로 형 변환하는 것이며, 다운캐스팅은 업캐스팅된 객체를 다시 원래의 자식 클래스 타입으로 형 변환하는 것이다.

다운캐스팅의 목적은 업캐스팅한 객체를 다시 자식 클래스 타입의 객체로 되돌리는데 목적을 둔다.

다운캐스팅의 진정한 의미는 부모 클래스로 업캐스팅된 자식 클래스를 복구하여, 본인의 필드와 기능을 회복하기 위해 있는 것이다. 즉, 원래 있던 기능을 회복하기 위해 다운캐스팅을 하는 것이다.

다운캐스팅을 할 때는 업캐스팅된 객체가 원래 자식 클래스의 인스턴스인지를 instanceof 연산자로 확인하는 것이 안전하다.

Auto Boxing / Auto Unboxing

기본 데이터 타입과 해당 래퍼 클래스(Wrapper Class) 간의 자동 변환을 지원하는 기능이다.

1. Auto Boxing : 기본 데이터 타입을 해당하는 래퍼 클래스의 객체로 자동으로 변환하는 과정을 말한다. 이를 통해 기본 데이터 타입의 값을 래퍼 클래스로 감싸는 작업을 직접 수행하지 않아도 된다.

- 기본 데이터 타입을 래퍼 클래스의 인자로 전달할 때

- 기본 데이터 타입을 컬렉션에 추가할 때 (ex. list.add(1))

* 컬렉션은 자바에서 여러 개의 객체를 담을 수 있는 자료구조를 말한다.

자바에서는 제네릭(Generic)을 이용하여 컬렉션에 저장되는 객체의 타입을 명시할 수 있다. 제네릭을 사용하면 컴파일 시 타입 안정성을 보장할 수 있다. 참조 타입만을 지원한다.

 

2. Auto Unboxing : 래퍼 클래스의 객체를 해당하는 기본 데이터 타입으로 자동으로 변환하는 과정을 말한다. 이를 통해 래퍼 클래스의 객체를 기본 데이터 타입으로 편리하게 사용할 수 있다.

- 래퍼 클래스의 객체를 기본 데이터 타입으로 할당할 때

- 래퍼 클래스의 객체를 기본 데이터 타입으로 연산할 때

 

NPE를 주의하여야 한다. 래퍼클래스가 NULL이면 래퍼클래스 + 기본타입 = NPE 발생

 

작성한 코드에 불필요한 auto casting이 반복적으로 이루어지고 있는지 확인하는 것은 대용량 서비스를 개발하는데 있어서 꼼꼼히 파악해야하는 요소이다.

 

 

 

 

컴퓨터 구조 기초

1. 중앙처리장치(Central Processing Unit, CPU) : 컴퓨터의 두뇌로서, 프로그램의 명령어를 해석하고 실행하는 역할을 수행한다. CPU는 제어장치(Control Unit), 연산 장치(ALU), 레지스터(Register) 등으로 구성되어 있다.

 

2. 메모리(Memory) : 데이터와 명령어를 저장하는 공간이다. 주기억장치인 RAM(Random Access Memory)과 보조기억장치인 하드디스크 등이 있다. RAM은 읽기와 쓰기가 빠르며 임시로 데이터를 저장하는 역할을 한다.

 

3. 캐시 메모리(Cache Memory) : CPU와 주기억장치 사이에 위치하여 데이터의 빠른 접근을 돕는 역할을 한다. 캐시 메모리는 속도가 빠르고 작은 용량으로 구성되어 있으며, 주로 최근에 사용된 데이터를 저장한다.

 

4. 입출력장치(I/O Devices) : 사용자와 컴퓨터 간의 상호 작용을 위한 장치로, 키보드, 마우스, 모니터, 프린터 등이 속한다. 입출력장치는 데이터의 입력과 출력을 담당하며, 컴퓨터와 외부 장치 간의 데이터 전송을 처리한다.

 

5. 시스템 버스(System Bus) : 컴퓨터 내부의 데이터 전송 경로이다. 주로 데이터 버스, 주소 버스, 제어 버스로 구성됨. CPU, 메모리, 입출력 장치 간의 데이터 전송과 제어 신호를 전달한다.

 

6. 명령어 실행 과정 : 프로그램이 실행되는 과정은 명령어의 흐름에 따라 진행된다. CPU는 메모리에서 명령어를 읽어와 해석하고, 해당하는 연산을 수행하거나 데이터를 처리한다. 이때 CPU는 레지스터를 사용하여 데이터를 임시로 저장하고 연산을 수행한다.

 

7. 파이프라인(Pipeline) : CPU의 성능을 향상시키기 위해 명령어 실행을 여러 단계로 나누어 동시에 처리하는 기술이다. 파이프라인은 명령어를 순차적으로 처리하는 것보다 처리 속도를 높일 수 있으며, 여러 명령어가 동시에 실행될 수 있다.

 

8. 캐시의 작동 원리 : 캐시는 메모리 접근 속도를 향상시키기 위해 사용되는 고속 버퍼이다. 데이터를 읽을 때 먼저 캐시에서 데이터를 찾아보고, 캐시에 데이터가 없을 경우 메모리에서 데이터를 읽어와 캐시에 저장한다.

 

9. 파이프라인의 구성 요소 : 파이프라인은 여러 단계로 이루어져 있으며, 각 단계는 명령어를 처리하는 특정한 기능을 담당한다. 예를 들어 명령어를 가져오는 단계, 명령어를 해석하고 실행하는 단계, 결과를 저장하는 단계 등으로 구성된다. 파이프라인은 명령어가 순차적으로 처리되는 것보다 처리 속도가 향상된다.

 

10. 인터럽트(Interrupt) : 컴퓨터가 실행 중인 작업을 중단하고 다른 작업을 처리하는 기능. 예를 들어, 입출력 장치에서 데이터를 전송할 때 인터럽트가 발생하여 CPU는 해당 작업을 처리하고 다시 원래의 작업으로 돌아간다. 인터럽트는 컴퓨터 시스템의 효율성과 응답성을 향상시키는 데 중요한 역할을 한다.

 

컴퓨터의 구성

 

 

 

CPU 작동원리

1. 명령어 해독

 - CPU는 메모리로부터 명령어를 가져와 해독하여 어떤 작업을 수행해야 할지 판단한다.

 - 명령어는 이진(binary) 형태로 표현되며, 해당 명령어의 의미를 해석하기 위해 CPU는 명령어를 해독하고 내부적으로 이해할 수 있는 형태로 변환한다.

 - 명령어는 일련의 비트로 구성되며, 이러한 비트를 해석하여 해당 명령어가 어떤 동작을 수행하는지 결정한다.

 

2. 명령어 실행 - 제어 유닛이 한다.

 - CPU는 명령어에 따라 적절한 작업을 수행한다. 

  - 연산(산술 논리 연산) : CPU는 산술 연산(덧,뺄,곱셈 등)과 논리 연산(AND, OR, NOT 등)을 수행할 수 있다. 이러한 연산은 레지스터에 저장된 데이터를 사용하여 계산된다.

 - 메모리 접근 : CPU는 메모리로부터 데이터를 읽거나 데이터를 메모리에 쓸 수 있다. 이를 통해 프로그램이 데이터를 읽고 쓰는 작업을 수행할 수 있다.

 - 제어 흐름 변경 : CPU는 분기(branch)와 점프(jump) 명령어를 사용하여 프로그램의 제어 흐름을 변경할 수 있다. 분기와 점프 명령어는 조건에 따른 다른 명령어를 실행하거나, 프로그램의 특정 위치로 이동할 수 있다.

 

3. 레지스터 사용

 - CPU는 데이터를 임시로 저장하고 처리하기 위해 레지스터라는 작은 기억 장소를 사용한다. 레지스터는 CPU 내부에 위치하며, 고속으로 접근할 수 있는 저장소이다.

 - 레지스터는 다양한 유형과 크기를 가지며, 주로 데이터를 저장하고 연산에 사용된다. 예를 들어, 산술 연산을 수행할 때 레지스터에 저장된 데이터를 사용하여 계산을 수행하고, 결과를 다시 레지스터에 저장할 수 있다.

 - 레지스터는 빠른 데이터 액세스를 제공하기 때문에 CPU 성능에 중요한 역할을 한다. 레지스터의 수와 크기는 CPU의 설계에 따라 다를 수 있다.

 

4. 제어 유닛(Control Unit)

 - CPU의 제어 유닛은 명령어의 실행을 제어하고, 다른 하드웨어 구성 요소와의 상호 작용을 조정한다.

 - 제어 유닛은 명령어를 순서대로 실행하고, 다음에 실행할 명령어를 결정한다. 이를 위해 제어 유닛은 명령어 포인터를 사용하여 다음 명령어의 위치를 추적한다.

 - 명령어를 해독하고, 해당 명령어에 대한 적절한 동작을 수행하기 위해 다른 하드웨어 구성 요소와의 상호 작용을 조정한다. 예를 들어, 제어 유닛은 연산 장치와 메모리 장치 간의 데이터 이동을 조정하고, 분기와 점프 명령어에 따라 프로그램의 흐름을 변경한다.

 

5. 클럭 신호

 - CPU는 클럭 신호라는 정기적인 신호에 의해 동작한다. 클럭 신호는 CPU의 작업을 동기화하기 위해 사용된다.

 - 클럭 신호는 일정한 주기로 발생하며, 각 클럭 신호의 도달마다 CPU는 다음 단계를 수행한다. 클럭 속도는 CPU의 동작 속도를 결정하며, 속도가 높을수록 CPU는 더 많은 작업을 수행할 수 있다.

캐시 메모리

 - 주 기억장치인 RAM보다 빠른 속도로 데이터에 접근할 수 있어 프로세서의 성능을 향상시키는 역할을 한다.

 - 작고 고속인 특징을 가진다.

 - 주로 계층적인 구조로 설계되어 있다.

 - 가장 가까운 위치에는 L1 캐시가 있고, 그 다음으로 L2, L3 등의 레벨이 존재한다. 레벨이 증가할수록 용량은 커지지만 접근 속도는 상대적으로 느려진다.

 - 적중(hit)과 실패(miss)라는 개념을 가지고 있다. hit은 CPU가 요청한 데이터를 캐시에서 바로 찾아내는 경우를 말하며, 실패는 캐시에 데이터가 없어 주 기억장치에서 가져와야 하는 경우를 말한다. 이때 실패가 발생하면 새로운 데이터를 캐시에 저장하고, 이후에 동일한 데이터를 요청할 때는 hit가 발생한다.

 - 계층적인 구조와 알고리즘을 통해 데이터의 적중률을 높이는 것이 중요하다.

 

'Question' 카테고리의 다른 글

DAY 4  (0) 2024.03.02
DAY 3  (0) 2024.03.02
DAY 2  (0) 2024.02.16

BELATED ARTICLES

more