티스토리 뷰

반응형

상속과 다형성, Override는 자바의 객체지향에 있어서 중요한 개념들이다.

어느정도 개념은 알고 있지만, OS관점? 메모리 구조 관점에서 보았을 때,

어떤식으로 작동이 되는 지 원리를 알고 싶었고, 공부하게 되었다.

 

 

 상속 

상속관계인 클래스에서는 자식 클래스가 부모 클래스의 변수와 메서드를 사용할 수 있다.

이게 어떻게 가능한걸까?

 

JVM 메모리 구조에는 Runtime Data Area가 있다.

프로그램 수행을 위해 OS로부터 별도로 할당받은 메모리 공간이며, 

PC 레지스터, JVM 스택, Native 메서드 스택, 메서드 영역, 힙으로 공간이 나뉘어져있다.

 

자세한 내용은 아래 글을 참고하면 된다.

 

[ Java ] JVM란? 자바의 실행 원리 알아보기

1. JVM이란? JVM은 자바 가상 머신으로 Java Virtual Machine의 줄임말이다. Java는 운영체제(OS)에 구애받지 않고 실행할 수 있는데 , JVM이 그 역할을 한다. 자바 프로그램은 컴퓨터가 이해할 수 있는 언어(

codingwell.tistory.com

 

5가지 공간 중 메서드 영역은 클래스 정보를 처음 메모리 공간에 올릴 때 초기화 되는 대상을 저장하기 위한 공간이다.

모든 스레드가 공유하는 영역이고, 클래스, 인터페이스, 메서드 정보, 필드, static 변수 등의 바이트 코드를 보관한다.

 

 

 

구체적인 예시와 메모리 구조를 통해 원리를 설명하겠다.

class Tv {
    boolean power;
    int channel;

    void power() {}
    void channelUp() {}
    void channelDown() {}
}

class CaptionTv extends Tv {
    String text;
    void caption() {}
}
CaptionTv c = new CaptionTv();

 

Tv 클래스와 CaptionTv 클래스는 상속관계이고 자식 클래스로 인스턴스 c를 생성하였다.

메모리 구조에선 어떤일이 일어날까?

 

1. Runtime Data Area의 메서드 영역에 이미 Tv 클래스 정보와 CaptionTv 클래스 정보가 들어있다.

2. 인스턴스를 생성하면 스택에 c 참조변수가 생성되고, CaptionTv 생성자가 동작한다.

3. CaptionTv는 Tv를 상속받았으므로, c의 생성자 동작 시에 super()도 동작되어, 부모 클래스인 Tv의 생성자도 호출된다.

4. 이에 따라 힙에 CapitonTv 객체가 생성된다.(부모 클래스의 멤버를 상속받아 포함하고 있음)

5. 스택에 있는 참조변수 c는 대입 연산자(=)를 통해 힙에 생성된 CaptionTv 객체의 주소를 담고 있다.

 

🔥 주의할 점
많은 블로그에서 잘못 다루고 있는 내용을 발견했다.
위의 4번 과정에서 힙에 자식과 부모 클래스 인스턴스 두개가 모두 생성된다고 언급한 블로그들이 많았다. 
이것은 잘못된 내용이다.(제대로된 정보를 얻으려고 애씀ㅠ)

자식 클래스에서 생성자를 호출할 때, 부모 클래스의 생성자도 호출된다.
그렇다고 힙에 두개의 인스턴스가 생성되는 것이 아니다. 자식 클래스의 인스턴스만 생성된다.
부모 클래스의 생성자는 따로 생성되지 않으며,
부모 클래스의 속성과 메서드를 자식 클래스에서 상속받아 사용할 수 있도록 초기화하는 과정이다.

 

 

 

이 때, 변수는 인스턴스가 생성될 때마다 새로 생성되지만, 같은 클래스의 메서드는 인스턴스가 달라도 같은 로직을 수행한다.

그래서 메서드는 메서드 영역에 위치하여, 메서드 호출 시에 메서드 영역의 주소를 참조하여 실행한다.

 

이로써 참조변수 c는 부모 클래스의 변수와 메서드에 접근할 수 있다.

 

 

 

 

 다형성 

1) 클래스 형 변환

Tv t = new Tv();  //Tv인스턴스 생성에는 Tv 타입의 참조변수를 사용
CaptionTv c = new CaptionTv();  //CaptionTv인스턴스 생성에는 CaptionTv 타입의 참조변수를 사용

 

Tv와 CaptionTv는 상속관계이며, 

두 클래스 각각 인스턴스를 생성했다.

 

보통의 상황에선 위와 같이 인스턴스를 생성한다. 인스턴스 타입과 참조변수 타입을 일치시켜 생성한다.

 

 

 

다형성은 한 타입의 참조변수로 여러 타입의 객체를 참조할 수 있도록 하는 것을 말한다.

즉, 부모클래스 타입의 참조변수로 자식클래스의 인스턴스를 참조할 수 있도록 하였다는 것이다.

이런것을 클래스 형 변환 중에서 업캐스팅이라고 한다.

Tv t = new CaptionTv();

 

이렇게 생성할 경우 참조변수 t는 힙에 생성된 CaptionTv 인스턴스의 주소값을 가지게된다.

이 인스턴스는 상속관계인 부모의 모든 멤버를 상속받아 가지고 있다.

 

여기서 t는 Tv클래스에 선언된 멤버들만 접근할 수 있다.

업캐스팅을 통해 하위 클래스 객체를 상위 클래스 타입으로 취급되도록 했기 때문에,

상위 클래스에서 정의한 멤버만 접근가능한 것이다.

 

CaptionTv 인스턴스에는 상속받은 멤버들뿐만 아니라 CaptionTv 멤버 변수들도 모두 존재하지만

컴파일러가 접근할 수 있는 멤버를 Tv 클래스 멤버로 제한하고 있다.

위의 예시에서는 CaptionTv의 text와 caption()은 사용할 수 없다.(자식 클래스에서 추가로 정의된 멤버)

 

이처럼 참조변수의 타입에 따라 사용할 수 있는 멤버의 개수가 달라진다.

참조변수 타입에 기반해 멤버 변수와 메서드에 접근할 수 있기 때문이다.

 

❓ 왜 참조변수 타입에 따라 접근 멤버를 결정할까
타입 안전성(Type Safety)을 보장하기 위해!
실행 시간에 발생할 수 있는 타입 관련 오류를 컴파일 시간에 미리 잡도록함

 

 

 

2) 오버라이드와 가상 메서드 

 

만약에 자식 클래스에서 오버라이드한 메서드가 있다면 어떻게 될까요?

여기서 오버라이드 개념 자체는 따로 설명하지 않겠습니다.

class Tv {
    boolean power;
    int channel;

    void power() {}
    void channelUp() {}
    void channelDown() {}
    void volumeUp() {
        System.out.println("tv volume up");
    }
}

class CaptionTv extends Tv {
    String text;
    void caption() {}

    @Override
    void volumeUp() {
        System.out.println("caption tv volume up");
    }
}

class Main {
    public static void main(String[] args) {
        Tv t = new CaptionTv();
        t.volumeUp();  //caption tv volume up
    }
}

 

이 때, 자식 클래스인 CaptionTv의 volumeUp()이 출력됩니다.

위에서는 참조변수의 타입이 Tv이기 때문에 CaptionTv의 변수와 메서드는 접근할 수 없다고 했는데,

오버라이딩한 메서드가 어떻게 작동할 수 있을까요?

 

 

 

 

가상 메서드 테이블을 이용하기 때문이다.

가상 메서드 테이블은 각 클래스마다 존재하고, 클래스가 메모리를 할당받을 때

자신의 메서드와 상속받은 모든 메서드의 주소를 모아 정리한 테이블을 말한다.

 

JVM이 클래스를 로딩할 때, 메서드 영역에 메서드를 할당하고 가상 메서드 테이블을 생성한다.

 

가상 메서드 테이블은 각 메서드 이름과 실제 메모리 주소가 짝을 이룬다.

어떤 메서드가 호출되면, 이 테이블에서 주소 값을 찾아서 메서드 바이트코드를 수행한다.

JVM의 메서드 영역

 

편의를 위해 Tv클래스의 channelUp()과 channelDown()은 생략했습니다.

위 그림에서 보듯 재정의된 volumeUp() 메서드는 서로 다른 메서드 주소를 가지게 되고,

실제 인스턴스에 해당하는 메서드를 호출하게 된다.

 

 

 

 

힙에 생성된 객체는 다음과 같이 가상 메서드 테이블의 주소를 가진다.

각 클래스의 객체가 생성될때마다 가상 메서드 테이블을 생성하는 것이 아니라, 

같은 클래스의 모든 객체들이 메서드 영역의 같은 테이블을 공유한다.

힙에 생성되는 인스턴스의 구조

 

이를 통하여 가상 메서드 테이블에 접근하고, 가상 메서드 테이블에서 일치하는 메서드를 탐색한다.

찾은 메서드의 메서드 주소 즉, 실제 메모리 주소에 접근하여 바이트코드를 실행한다.

이렇게 프로그램이 실행된 후에, 가상 메서드 테이블을 통해 코드와 메모리가 바인딩 되는 이러한 과정을 동적 바인딩이라고 한다.

 

따라서, 위의 예시에서 생성한 인스턴스인 CaptionTv의 재정의된 메서드가 실행되는것이다.

반응형

'Java > 개념정리' 카테고리의 다른 글

[Java] 애노테이션(annotation)  (1) 2021.10.18
[Java] enum(열거형)  (0) 2021.10.14
[Java] 멀티쓰레드 프로그래밍  (0) 2021.10.14
[Java] 예외 처리  (1) 2021.10.06
[Java] 인터페이스(interface)  (0) 2021.10.06
댓글
반응형
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday