부자 되기 위한 블로그, 머니킹

자바 탄생 이유

다양한 OS에서 독립적으로 실행시키기 위해서, 이가 가능한 이유는 JVM(Java Virtual Machine) 덕분이다. JVM은 컴파일된 코드를 실행시켜주는 가상의 컴퓨터 역할을 한다.

 

 

 

자바 컴파일 과정

  1. 개발자가 자바 소스를 작성한다 (.java)
  2. 자바 컴파일러가 자바 소스파일을 컴파일한다. 이 때 나오는 파일은 자바 바이트 코드(.class)파일로 아직 컴퓨터가 이해할 수 없으며 자바 가상 머신이 이해할 수 있는 코드이다.
  3. 컴파일된 바이트코드를 JVM 클래스 로더에게 전달한다.
  4. 클래스 로더는 동적로딩을 통해 필요한 클래스들을 로딩 및 링크하고 런타임 데이터 영역, JVM의 메모리에 올린다.
  5. 실행엔진은 JVM 메모리에 올라온 바이트 코드들을 실행한다.
    1. 인터프리터 : 바이트 코드 명령어를 하나씩 읽어서 해석/실행, 실행속도가 느림
    2. JIT 컴파일러 : 바이너리 코드 전체를 컴파일하고 바이너리 코드로 변경한 후에 바이너리 코드로 직접 실행하는 방식

 

 

JVM 메모리 영역

  • Stack Area
    • 클래스 내의 메소드에서 사용되는 정보들이 저장되는 공간 (매개변수, 지역변수, 리턴값)
  • Methd Area
    • 클래스와 메소드, 인스턴스 변수 및 상수 정보등이 저장되는 공간
  • Heap Area
    • New 명령어를 통해 생성된 인스턴스와 배열 등 참조형 변수 저장 공간, GC의 대상이 됨
  • PC Register Area
    • JVM 명령의 주소값이 저장되는 공간
  • Native Method Stack Area
    • 자바 외에 다른 언어의 호출을 위해 할당되는 영역

 

 

Garbage Collection

GC는 JVM의 Heap Area만 다룬다. GC는 메모리 관리 역할을 담당하는데 자동으로 사용하지 않는 객체를 소멸시키는 역할을 한다. GC 동작 과정 중 stop-the-world가 발생하는데 이는 GC를 실행하기 위해 JVM이 앱 실행을 멈추는 것이다.

GC를 해도 더이상 사용 가능한 메모리가 없을 경우에는 OutOfMemoryError가 발생한다.

 

 

 

GC 메모리 해제 과정

 

  1. Mark
    1. GC가 메모리가 사용되는지 확인하고 모든 오브젝트는 스캔받으며 참조되는 객체, 참조되지 않는 객체로 분류한다.
  2. Sweep
    1. 참조되지 않는 객체를 제거하고 메모리를 반환한다. 메모리 Allocator는 반환되어 비어진 블록의 참조 위치를 저장하고 새로운 오브젝트 선언시 할당되도록 한다
  3. Compact
    1. 퍼포먼스 향상을 위해 남은 객체들을 묶는다. 이들의 묶음이 생기면서 공간이 생겨 새로운 메모리 할당시 더 빠르게 진행할 수 있다.

 

  1. 어떠한 새로운 객체가 들어오면 Eden Space에 할당한다.
  2. Eden Space가 가득차면 minor gc가 발생한다.
  3. 참조되는 객체들은 첫번째 survivor space로 이동되고 비 참조 객체는 Eden space가 clear시 반환된다.
  4. 다음 minor gc 가 일어나면 1-2의 과정이 반복되다가 Survivor 영역이 가득차게 되면 살아남은 객체를 다른 Survivor 영역으로 이동시킨다.
  5. 이러한 과정을 반복하여 살아남은 객체는 Old 영역으로 이동한다.
  6. Old 영역의 메모리가 부족해지면 Major GC가 발생하여 사용하지 않는 객체들을 claer 한다. 이 때 Old 영역Young 영역보다 메모리 크기가 크기 때문에 실행 속도가 느리다
  7. GC 가 발생하는 동안 stop the world가 발생하고 Major GC로 인한 stop the world를 멈추기 위해 다양한 GC 알고리즘을 사용한다.

 

 

 

GC 알고리즘

  • Serial GC
    • Mark, Sweep, Compact 작업이 실행되는데 쓰레드를 한개만 사용하므로 피해야 한다
  • Parallel GC
    • Serial GC와 동일하지만 여러개의 쓰레드를 통해 GC를 수행하여 오버헤드를 줄여준다.
  • CMS GC
    • Parellel GC와 거의 동일하지만 Mark, Sweep 알고리즘을 동시에 하도록 수행한다.
  • G1 GC
    • Heap을 동일한 크기의 Region으로 나눈 후, 가비지가 많은 Region에 대해 우선적으로 GC 수행

 

오버헤드

프로그램의 실행 중 추가적인 시간과 메모리, 자원이 소요되는 것

ex ) 10초가 걸릴 기능이 다른 간접적인 원인으로 20초가 걸리면 오버헤드는 10초이다.

 

 

 

자바 함수 호출 방식

함수 호출 방식 종류

  • call by value : 함수가 호출 시 전달되는 파라미터가 값을 복사하여 함수 인자에게 전달
  • call by reference : 함수 호출 시 인자로 전달되는 변수의 레퍼런스 형식으로 전달 (포인터 )

자바는 기본적으로 call by value 방식을 선택하고 있다. 따라서 원본 데이터가 변형될 가능성이 전혀 없다 ( 자바는 call by reference 방식으로 넘길 방법이 없다 )

 

 

 

Casting

캐스팅은 변수가 원하는 정보를 다 가지고 있는 것이다. 캐스팅은 다형성과 상속을 활용하기 위해 필요하다.

형변환

  • 묵시적 형변환 : 캐스팅 자동으로 발생하게끔 하는 것 ( 업캐스팅 )
Parent p = new Child();
  • 명시적 형변환 : 캐스팅할 내용을 적어주어 형변환 하는 경우 ( 다운 캐스팅 )
Parent p = new Child();
Child c = (Child) p;

 

 

Auto Boxing & Auto UnBoxing

자바는 컴파일러가 박싱과 언박싱이 필요하 상황에 자동으로 처리해 줌

// 오토 박싱
int i = 10;
Integer num = i;

// 오토 언박싱
Integer num = new Integer(10);
int i = num;

 

 

JAVA에서 Thread 활용

멀티태스킹은 두 가지 이상 작업을 동시에 하는 것을 말한다. 자바에서는 하나의 프로세스 안에서 여러개의 스레드가 동시에 작업을 수행할 수 있도록 지원한다.

 

Thread 구현

  • Runnable 인터페이스 상속
  • Thread 클래스 상속
// 스레드 클래스 구현
public class ThreadTest implements Runnable {
    public ThreadTest() {}
    
    public ThreadTest(String name){
        Thread t = new Thread(this, name);
        t.start();
    }
    
    @Override
    public void run() {
        for(int i = 0; i <= 50; i++) {
            System.out.print(i + ":" + Thread.currentThread().getName() + " ");
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

public class MyThread extends Thread {
    @Override
    public void run() {
        // 수행 코드
    }
}

// 스레드 생성
public static void main(String[] args) {
    Runnable r = new MyThread();
    Thread t = new Thread(r, "mythread");
}

Thread는 run이 아닌 start로 실행해야 한다.

실질적으로 멀티태스킹하려면 두개 이상의 call stack을 생성하여 콜스택을 번갈아가면서 작업을 해야 하는데 run의 경우 main의 call stack을 사용하기 때문에 새로운 콜스택을 만들어 contexts switching을 통해 동작하게끔 하는 start 함수를 사용해야 한다.

 

 

스레드 실행 제어

  • New : 스레드가 생성되어 start가 호출 되지 않음
  • Runnable : 실행 혹은 실행 가능한 상태
  • Blocked : 동기화 블록에 의해 일시 정지된 상태
  • Waiting, time_wating : 실행 될 수 없는 일시 정지 상태
  • Terminated : 스레드 작업 종료된 상태

 

동기화

여러 스레드가 같은 프로세스의 자원 공유하면서 작업하면 서로의 작업이 다른 작업에 영향을 주기 때문에 동기화가 필수적이다.

스레드 동기화를 위해 임계 영역을 정해 lock을 하나의 스레드에게만 빌려주고 작업 수행이 완료되면 lock을 반납한다.

  • 임계 영역 : 공유 자원에 하나의 스레드만 접근하도록 ( 같은 프로세스 )
  • 뮤텍스 : 공유 자원에 하나의 스레드만 접근하도록 ( 다른 프로세스 스레드도 가능 )
  • 이벤트 : 특정 사건 발생시 다른 스레드에게 알림
  • 세마포어 : 한정된 자원에 여러 스레드가 접근시 접근 제한
  • 대기 가능 타이머 : 특정 시간 되면 대기 중의 쓰레드 깨움

 

 

 

 

Stream vs Collection

Collection

  • 모든 값을 메모리에 저장함. 따라서 Collection에 추가하려면 미리 연산 완료되어야 함
  • 외부 반복을 통해 요소 가져옴
  • 외부 반복

Stream

  • 요청시에 요소를 계산. 추출 요소만 선언해주어 반복처리 진행
  • 스트림에 요소를 추가 제거하는 작업 불가
  • 내부 반복

 

Stream 활용 예제

  • map()
List<String> names = Arrays.asList("Sehoon", "Songwoo", "Chan", "Youngsuk", "Dajung");
    
names.stream()
    .map(name -> name.toUpperCase())
    .forEach(name -> System.out.println(name));
  • filter()
Stream<Integer> numbers = Stream.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
Optional<Integer> sum = numbers.reduce((x, y) -> x + y);
sum.ifPresent(s -> System.out.println("sum: " + s));
  • reduce()
Stream<Integer> numbers = Stream.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
Optional<Integer> sum = numbers.reduce((x, y) -> x + y);
sum.ifPresent(s -> System.out.println("sum: " + s));

// sum 55
  • collect()
System.out.println(names.stream()
                   .map(String::toUpperCase)
                   .collect(Collectors.joining(", ")));

 

 

 

 

String vs String Buffer vs String Builder

  1. String
    1. new 연산을 통해 생성된 인스턴스는 메모리공간은 변하지 않아 GC로 제거되어야함
    2. 이 때문에 문자열 연산 시 새로 객체를 만드는 OverHead 발생
    3. 객체가 불변해 Multithread에서 동기화 신경 쓸 필요 없음
  2. StringBuffer, StringBuilder
    1. new 연산시 클래스를 한번만 만든다
    2. 문자열 연산시 새로 객체 만들지 않으며 크기를 변형
    3. StringBuffer, StringBuilder 클래스 메서드 동일
    4. StringBuffer는 Thread-safe하며 StringBuilder는 Thread-safe하지 않음
  1.