본문 바로가기
Java관련/Java

자바 상속의 메모리 할당 과정

by devstep 2022. 9. 5.

자바 상속의 메모리 할당 과정

최근 자바의 정석 상속 부분을 다시 읽으며 이해되지 않은 문장들이 있었고 이에 자바 상속의 메모리 할당 과정에 대해 조사하게 되었습니다.

이해한 내용을 바탕으로 제 생각이 더해진 내용을 적어 봅니다.
추가적으로 도움이 될만한 자료나 방향이 다른 내용은 댓글이나 방명록에 남겨주시면 감사하겠습니다 :)

 

 


아래 자바 코드가 메모리에 어떤 순서로 어디에 할당되는지 알아보도록 하겠습니다.

public class Person {

    public int age;
    public String name;

    public Person() {
        System.out.println("parent class");
        say();
    }

    public void say() {
        System.out.println("Someone speaks.");
    }  
}

class Student extends Person {

    public String school;

    public Student() {
        System.out.println("Subclass");
    }

    public void say() {
        System.out.println("student:" + name + "," + age);
    }
}

class Demo {
    public static void main(String[] args) {
        Student s = new Student();
        s.age = 20;
        s.name = "tom";
        s.say();
    }
}
  • output
  • Parent class student:null,0 Subclass student:tom,20

 


스택과 힙의 할당 과정을 눈으로 확인

이 사이트에서 위 코드를 붙여넣고 실행하면 자바 실행과정에서의 스택과 힙의 할당 과정을 눈으로 확인할 수 있습니다.
전체적인 메모리 할당 과정 흐름을 그림으로 살펴볼 수 있습니다.

  • 사이트에서 코드를 실행 시 : Demo클래스는 없애고 메인 메소드를 public 클래스 안에 붙여넣으면 됩니다.

 

 


위 코드의 명령어 실행과정을 조금 더 상세하게 살펴보겠습니다.
할당 과정을 그림으로 확인한 사이트는 힙과 스택만 표현되어 있는데요. 아래 설명글에는 메소드 영역이 포함됩니다.

사실 메소드 영역도 논리적인 표현이고 실제로는 다를 수 있습니다. 그리고 자바 버젼에 따라 명세도 달라집니다. 세부적인 사항이 궁금하면 기계인간 John Grip님의 글을 참고해보세요.
여하튼 필드및 메소드 데이터, 메소드/생성자 코드는 어딘가에 저장되어 있고 해당 데이터가 저장된 곳을 메소드 영역(method area)라고 칭하겠습니다.

명령 실행과정

  1. JVM은 Demo클래스를 먼저 로딩합니다. 로딩은 바이트코드를 바이너리 형태로 변환해 method ared에 로딩합니다. 그리고 링크의 클래스 정보를 추출하기 위해 method area에 Class 객체(getClass()하면 얻을 수 있는 객체)를 생성합니다.(링크의 preparation과정)
  2. method area에 저장된 바이트코드를 통해 JVM은 main 메소드를 실행합니다. main 함수가 스택에 push 됩니다.
  3. Student s = new Student();를 수행할 때 heap 영역에 Student 인스턴스를 위한 공간을 할당합니다.
  4. 부모 클래스를 먼저 구현해야하므로 JVM은 Person 클래스 정보인 Class 객체를 method area에 로딩합니다. 그리고 부모 클래스 멤버 변수를 힙의 서브클래스 공간에 초기화합니다.
    • 처음 궁금했던 부분은 생성자가 호출되는데 그럼 힙에 부모클래스 객체가 생성되는 것 아닌가라고 생각했지만, 생성자가 호출로 method area에 부모클래스 정보인 Class객체가 생성되고 부모클래스 멤버변수는 하위클래스 힙 공간에 초기화됩니다.
      new로 인스턴스를 위한 공간이 이미 할당되었고 (부모 객체까지 할당할 공간), 생성자 호출로 초기화가 됩니다.
      그 후 Student 클래스를 method area에 로드하고 Class 객체를 할당하고, 서브클래스의 멤버변수를 초기화합니다.
  5. age, name 멤버변수는 부모클래스로부터 상속받은 변수이므로, s.age=20;, s.name="tom"; 이 두 구문은 서브클래스안의 부모 객체에 저장됩니다. 참조변수 s는 힙 영역 안의 서브클래스 객체를 찾기위해 참조를 보유합니다. 그런다음 name, age를 할당한다.
  6. say() 메서드 호출로 JVM은 참조변수 s의 레퍼런스를 통해 힙에서 인스턴스를 찾는다. 메소드 영역에서 인스턴스가 보유한 이 클래스의 참조(변수명 klass로 추정되는 Class 객체)를 통해 클래스의 타입 정보를 찾는다. 그리고 say() 메소드를 찾는다. say() 메소드는 스택에 push된다. say() 메소드의 bytecode를 실행한다.
    이 부분이 다이나믹 메서드 디스패치가 이뤄진 부분으로 다시 정리하면, s가 가리키는 힙 영역의 인스턴스에는 klass 변수가 있고, klass변수는 method area에 있는 Class 객체를 가리킵니다. 해당 타입의 say()메서드 바이트코드를 찾아 실행합니다.
  7. say() 메소드가 실행된 후 say()메소드는 스택에서 pop된다. 그리고 프로그램 실행은 main() 메소드로 돌아온다. main() 메소드가 실행된 후에는 스택에서 pop되고 메인 쓰레드가 죽고, JVM 인스턴스가 죽고 프로그램이 끝난다.

명령 실행과정 요약

부모와 같은 메소드는 오버라이딩됩니다. 그러나 변수는 오버라이딩되지 않습니다. 만약 서브클래스가 부모클래스와 같은 이름의 변수를 선언하면 서브 클래스는 같은 이름으로 2개의 변수를 갖습니다. 하나는 서브클래스의 인스턴스 객체에 저장되고, 하나는 서브클래스의 부모에 저장됩니다. 부모클래스의 private 변수는 서브클래스의 부모 객체에 상속되고 초기화 됩니다. 그러나 visible하지 않습니다.
다형성이 발생할 때 메소드는 (힙에 생성된 객체안의 klass 변수로) 재 매칭되는 것이 필요합니다.(동적 메서드 디스패치)

 

서브클래스에 같은 이름의 멤버변수가 존재할 경우

Parent parent = new Child();
자식 클래스에 같은 이름의 멤버변수가 존재할 경우 객체의 레퍼런스 참조가 부모 클래스인 경우 부모클래스 레퍼런스 참조로는 자식 클래스의 멤버변수에 직접 접근하지 못합니다. 그리고 자식 클래스의 어떤 멤버 변수에도 직접 접근하지 못합니다.
그럼에도 불구하고 new Child를 통해 자식 클래스의 멤버변수는 힙 영역안에 할당됩니다.
힙 영역에 할당되었다면 사용할 수가 있다는 것인데요. 그 방법은 오버라이딩 된 메서드를 통해 자식 클래스의 멤버변수에 간접적으로 접근할 수 있습니다.
[ ] 이렇게 설계해서 사용하는 경우가 있는지 궁금하네요.

 

상속의 의미

상속된다는 것은 메모리 공간이 할당된다는 의미라고 생각합니다. 생성자와 초기화 블럭은 상속되지 않고 멤버만 상속됩니다.
멤버 변수는 힙 영역의 인스턴스에 생성되며, 멤버 메소드 정보는 메소드 영역에 생성됩니다.
그렇기 때문에 Object를 모든 인스턴스에 포함하더라도 힙 영역안 각기 다른 인스턴스의 할당 공간에 영향을 주지 않는 것은 Object에는 멤버 변수가 없기 때문입니다. 그리고 Object 메서드를 호출하기 위한 Object 클래스의 메타정보와 코드는 모든 인스턴스가 공유할 수 있기 때문에 Object클래스를 모든 클래스가 상속하더라도 메모리 공간 할당량에 영향을 주지 않습니다.

 

검색어

  • 자바 오브젝트 생성과정 JVM
  • Java inheritance memory allocation
  • How does inheritance work in memory?

 


참고자료


그외 키워드

댓글