Unit

  • Java의 void와 대응되는 개념이다.
  • 코틀린은 함수에 리턴이 없으면 Unit타입으로 추론한다.
    • void가 아니라 Unit객체를 리턴하기 때문에 모든 함수가 표현식이 될 수 있다.
  • Unit 클래스는 toString() 함수를 가지고 있다. 
    • 그렇게 유용하지 않지만, 반환값이 없을 때 생기는 문제점을 처리해준다.
      • ex) 출력하면 kotlin.Unit으로 나온다. 

Any

  • Java의 Object에 대응되는 클래스이다.
  • toString(), equals(), hashCode() 함수를 가지고 있다.
  • 모든 코틀린 클래스의 조상(Superclass)이다. 
  • Any는 확장함수를 통해 특별한 메소드들을 제공한다.
    • Pair를 만들기 위한 to()함수
    • let(), run(), apply(), also() 등

Nothing

  • 함수가 아무것도 리턴하지 않을 경우 리턴하는 타입이다. 
    • Java에는 대응되는 타입이 없다.
  • 코틀린에서는 함수가 리턴하지 않을 때, Unit을 사용한다고 했는데 무슨말인가?
    • 가끔 함수가 진짜로 아무것도 리턴하지 않는 상황이 있다.
    • 리턴이라는 행위 자체를 하지 않는 것이다.
      • 무한 루프
      • 예외 던지기
  • Nothing은 인스턴스가 없고, 값이난 결과가 영원히 존재하지 않는다는 것을 나타낸다.
fun computeSqrt(n: Double): Double {
    if (n >= 0) {
        return Math.sqrt(n)
    } else {
        throw RuntimeException("No negative please")
    }
}

fun throwException(): Nothing {
	throw Exception()
}
  • 예외를 던지는 것은 리턴하는 행위가 아니다. 이런 경우 아무 것도 리턴되지 않는 것이다. 
  • computeSqrt()에서 if 문은 Double을 반환하고, else는 Nothing이다. 
    • 근데 반환값은 왜 Double일까?
      • Nothing은 모든 클래스로 대체될 수 있다. 모든 클래스의 서브(자식)클래스이기 때문이다.

'Programming > 언어' 카테고리의 다른 글

[Kotlin] Data Class  (0) 2023.02.15
[Kotlin] Collection의 View  (0) 2023.02.13
[Kotlin] 타입추론  (0) 2023.02.12
[Kotlin] 표현식과 명령문  (0) 2023.02.12
Java 코드가 실행되는 과정  (0) 2021.12.26

코틀린은 정적 타입 언어이다.

정적 타입이라는 것은 컴파일 시점에 변수의 타입이 결정되어있는 것을 말한다.

코틀린은 정적 타입 언어이지만, 타입추론을 지원하기 때문에 타입을 명시하지 않고도 사용할 수 있다.

val greet = "hello"

println(greet) // hello
println(greet::class) // class kotlin.String
println(greet.javaClass) // class java.lang.String

greet = 0
  • 타입 추론은 컴파일 시간에 변수 초기화 시, 타입을 추론한다.
    • greet은 문자열로 초기화가 되어 String타입으로 추론되었다.
    • 다른 타입을 할당하면, 컴파일 오류가 난다.
      • 애초에 val 이기 때문에 재할당이 불가능한 것도 있다.
  • greet::class, greet.javaClass는 실행시간에 참조된 객체의 타입을 나타낸다.
    • greet::class는 참조되고 있는 객체의 코틀린 클래스를 나타낸다.
    • greet.javaClass는 참조되고 있는 객체의 자바 클래스를 나타낸다.

'Programming > 언어' 카테고리의 다른 글

[Kotlin] Collection의 View  (0) 2023.02.13
[Kotlin] Unit, Any, Nothing 클래스  (0) 2023.02.12
[Kotlin] 표현식과 명령문  (0) 2023.02.12
Java 코드가 실행되는 과정  (0) 2021.12.26
Garbage Collector 찍먹하기  (0) 2021.12.26

표현식 vs 명령문

  • 표현식은 값을 반환하고, 어떤 상태도 변화시키지 않는다.
  • 명령문은 아무것도 반환하지 않으며, 상태를 변화시키거나 변수를 변하게 하고, 파일 작성, 데이터베이스 업데이트, 네트워크 요청 등의  작업을 수행한다.

Java, C#, JavaScript 등은 표현식보다는 명령문을 더 많이 가지고 있다. (if, for, try 등)

반면, Ruby, F#, Haskell, Groovy 등은 표현식을 더 많이 가지고 있다.


코틀린의 if와 try-catch 문은 표현식이다.

자바와 같이 명령문 형태로 쓸 수도 있지만, 표현식 형태로 쓰는 것이 더 좋다.

fun canVote(name: String, age: Int): String {
    var status: String
    if (age > 18) {
    	status = "yes, please vote"
	} else {
    	status = "nope, please come back"
    }
    return "$name, $status"
}
  • 먼저 명령문 형태로 쓴 코틀린을 보자.
    • 명령문은 반환값을 주지 않기 때문에 조건에 따른 결과를 얻기 위해 뮤터블 변수가 필요해졌다.

 

val status = if (age > 18) "yes, please vote" else "nope, please come back"
  • 코틀린의 if문은 표현식이기 때문에 아래와 같이 작성할 수 있다. 
    • 변수를 이뮤터블하게 사용할 수 있고, 타입추론도 사용할 수 있다.

 

fun tryExpr(blowup: Boolean): Int {
	return try {
    	if (blowup) {
        	throw RuntimeException("fail")
        }
        2
    } catch (e: Exception) {
    	4
    }
}
  • try-catch 도 표현식으로 사용할 수 있다.
    • try문, catch문의 마지막 부분이 반환값이 된다.

a = b = c // error
  • Java는 할당을 표현식으로 취급하지만, 코틀린은 아니다.
  • 코틀린은 델리게이션(delegation)을 통해 변수를 get하거나 set하기 때문이다.
    • 추후 포스팅 예정

'Programming > 언어' 카테고리의 다른 글

[Kotlin] Unit, Any, Nothing 클래스  (0) 2023.02.12
[Kotlin] 타입추론  (0) 2023.02.12
Java 코드가 실행되는 과정  (0) 2021.12.26
Garbage Collector 찍먹하기  (0) 2021.12.26
객체지향에서 인터페이스란?  (0) 2021.12.26

프로그램 동작 원리

  • 기본적으로 소프트웨어가 작성되어 CPU에서 실행되기까지의 과정은 다음과 같다.
    • 소스파일 작성 → 컴파일러나 인터프리터가 어셈블리어로 번역 → 어셈블러가 기계어로 번역 → 목적 파일 생성 → Linking → loader module 생성 → loader → CPU execute
      1. 우리가 C나 C++ 등의 고급언어로 소스코드를 작성한다.
      2. 언어에 맞는 컴파일러(혹은 인터프리터)가 어셈블리어로 번역해준다.
      3. 어셈블리어로 번역된 파일을 어셈블러가 기계어 코드(0,1)인 목적 파일로 번역한다.
      4. 규모가 어느정도 있는 프로그램이면 한 파일에 모든 소스코드가 있지 않다. 여러 파일로 나눠져 컴파일이 된 목적 파일들을 연결해야한다. 이 과정을 Linking이라고 한다.
      5. Linker에 의해 연결한 파일을 loader module이라고 한다.
      6. loader는 loader module을 메모리에 올린다.
      7. 메모리에 올라간 loader module을 CPU가 실행한다.

그럼 JVM은 어떻게 동작하는 것일까?

JVM의 동작 원리

  • Java 언어의 특징은 어떤 플랫폼, 운영체제에서도 동작하는 것이다. 앞에서 본 것처럼 목적 파일로 만들어져 바로 CPU에서 수행되는 것이 아니다.

JDK 구조

  • 우선 JDK의 구조부터 보자.
  • https://medium.com/@mannverma/the-secret-of-java-jdk-jre-jvm-difference-fa35201650ca
    • JDK > JRE > JVM
    • 이런 포함관계를 갖는다.
      • JDK(java development kit)
        • 컴파일러, 디버거, JRE가 있다.
          • JRE(java runtime environment)
            • JVM과 라이브러리가 있다.
          • JVM(java virtual machine)
            • https://ahea.wordpress.com/2017/05/25/자바개발자가-알아야-할-jvm과-garbage-collection/
            • JVM 내부에는 Class Loader, Runtime Data Areas, Execution Engine 등이 있다.

Class Loader

  • 클래스 로더는 바이트 코드(.class)를 받아서 필요한 클래스를 가져와 JVM의 메모리에 로드하고, 링킹하고 초기화 하는 과정을 수행한다.
  • 자바는 컴파일 타임에 모든 클래스를 로드하는 방식이 아닌, 런타임에 클래스가 필요하면(참조할 때) 클래스를 로드하고 링킹하는 동적로드를 한다. - JVM에서 동적로드를 담당한다.

Class Loader 특징

이제 JVM이 어떻게 class 파일을 기계어 코드로 변환하는지 알아보자.

  • 계층 구조 - JVM의 클래스 로더는 여러개가 있는데 이 클래스 로더끼리 부모-자식관계의 계층 구조를 이루고 있다.
  • 위임모델 - 게층 구조를 바탕으로 클래스 로더끼리 호출을 위임한다. 클래스를 로딩할 때, 최상위 클래스로더인 부트스트랩 클래스 로더를 확인하고 이 클래스 로더에 로딩하려는 클래스가 없다면 자식 클래스 로더로 책임을 넘긴다. - 위임 모델 다이어그램
  • https://d2.naver.com/helloworld/1230
    • 부트스트랩 클래스 로더
      • JVM이 처음 실행될 때 생성된다. 최상위 Object클래스와 Java api들을 로드한 - 익스텐션 클래스 로더(Extension Class Loader)
        • 기본 자바 API를 제외한 확장 클래스들을 로드한다.
          • 시스템 클래스 로더(System Class Loader)
            • 사용자가 작성한 클래스들(?), $CLASSPATH 내의 클래스들을 로드한다.
          • 사용자 정의 클래스 로더(User-Defined Class Loader)
            • 애플리케이션 사용자가 직접 코드 상에서 생성해서 사용하는 클래스 로더이다.
  •  
  • 가시성 제한 - 하위 클래스 로더에서는 상위 클래스로더의 클래스를 찾을 수 있다. - 반대는 안된다.
  • 언로드 불가 - 클래스 로더는 클래스를 로딩만 할 수 있다. - 이미 로드된 클래스를 언로드 할 수는 없다.

클래스 로딩 과정

  1. 로딩
    • 클래스 파일을 가져와서 JVM 메모리에 로드
  2. 링킹
    • 검증
      • 자바, JVM의 명세에 맞게 작성 되어 있는지 확인
    • 준비
      • 클래스가 필요로하는 메모리를 할당 (필드, 메소드 등)
    • 분석
      • 클래스의 상수 풀 내의 심볼릭 레퍼런스를 다이렉트 레퍼런스로 변경
        • 클래스 파일은 JVM이 프로그램을 실행할 때 필요한 API를 Link할 수 있도록 심볼릭 레퍼런스를 가진다. 심볼릭 레퍼런스를 런타임 시점에 메모리 상에서 실제로 존재하는 물리적인 주소로 대체하는 Linking 작업이 일어난다.
        • 심볼릭 레퍼런스는 참조하는 대상의 이름을 지칭하고, 클래스 파일이 JVM에 올라가게 되면 심볼릭 레펀선스는 실제 메모리 주소가 아닌 이름에 맞는 객체의 주소를 찾아서 연결하는 작업을 수행한다.
  3. 초기화
    • 클래스 변수들을 초기화한다. → static 필드들을 설정된 값으로 초기화 한다.

Runtime Data Areas

  • JVM이 OS위에서 실행되면서 할당받아 사용하는 메모리 영역이다. - 6개 영역으로 구분된다.
  • https://d2.naver.com/helloworld/1230
  • 쓰레드 마다 생성
    • PC Register
      • 현재 수행 중인 JVM의 명령어 주소
    • JVM Stack
      • 쓰레드가 시작될 때 생성
      • 스택 프레임을 저장하는 스택이다.
        • 메소드 스택
          • 지역변수, 매개변수, 반환 주소 등
    • Native Method Stack
      • 자바 외의 언어로 작성된 네이티브 코드를 위한 스택
  • 모든 쓰레드가 공유
    • Heap
      • 인스턴스를 저장하는 공간
      • 가비지 컬렉션 대상
    • Method Area
      • JVM이 시작될 때 생성된다.
      • JVM이 읽어 들인 각각의 클래스와 인터페이스에 대한 런타임 상수 풀, 필드와 메소드 정보, static 변수, 메소드의 바이트 코드 등이 포함된다.
    • Runtime Constant Pool
      • Method Area에 포함되는 영역이다.
      • JVM에서 가장 핵심적인 역할 수행
      • 클래스와 인터페이스의 상수, 메소드, 필드에 대한 모든 레퍼런스를 담고 있는 테이블
      • 메소드나 필드를 참조할 때, 이 테이블을 통해 실제 메모리상의 주소를 찾는다.

Execution Engine

  • JVM의 메모리에 올라온 바이트 코드들을 명령어 단위로 하나씩 실행한다.
  • 바이트 코드(.class)를 JVM 내부에서 실행할 수 있는 형태(기계어?)로 바꾼다.
  • 바꾸는 방법이 2가지가 있다
    • 인터프리터
      • 바이트 코드 명령어를 하나씩 읽고 해석해서 실행한다.
      • 하나하나의 해석은 빠르지만 전체 실행 결과는 느리다.
      • 바이트 코드는 기본적으로 인터프리터 방식으로 동작한다.
    • JIT(Just In Time)
      • 인터프리터의 단점을 보완하기 위해 도입된 JIT 컴파일러 이다.
      • 바이트 코드 전체를 컴파일하여 네이티브 코드로 변경한다.
        • 전체를 컴파일하는 과정이 인터프리터 보다 느리다.
        • 따라서 한 번만 실행되는 코드라면 컴파일 하지 않는다.
        • JIT 컴파일러를 사용하는 JVM들은 내부적으로 컴파일하려는 메소드가 얼마나 자주 실행되는지 체크하고 일정 수준 이상일 때 컴파일을 한다.

정리

  1. 자바 소스코드를 작성한다.
  2. JDK의 자바 컴파일러가 자바 파일을 바이트 코드로 변환한다.
  3. JRE 안의 JVM의 클래스 로더가 바이트 코드를 받아서 JVM의 메모리에 올리고, 필요한 클래스들을 로딩한다.
  4. 실행 엔진이 메모리 상에 있는 바이트 코드를 JVM 내부에서 실행할 수 있는 기계어 형태로 바꿔서 실행한다.

참고자료

https://yeon-kr.tistory.com/112

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

https://ahea.wordpress.com/2017/05/25/자바개발자가-알아야-할-jvm과-garbage-collection/

https://medium.com/@mannverma/the-secret-of-java-jdk-jre-jvm-difference-fa35201650ca

https://d2.naver.com/helloworld/1230

https://lkhlkh23.tistory.com/100

 

+ Recent posts