본문 바로가기
Java/Java Basic

[Java Basic] 13. Java 매서드 작성, Java 클래스와 메모리

by Rosmary 2022. 7. 5.
728x90
반응형

 

 

 

 

다른 프로그래밍 언어를 해 보셨던 분들이라면 프로그래밍에서 함수의 역할을 잘 알고 있을 것이다. Java의 클래스 내 매서드는 함수를 조금 다른 이름으로 부르는 명칭인데, 기존의 절차적 프로그래밍의 함수와는 약간의 차이가 있어서 아마 매서드(Method)라는 이름이 붙은것이 아닐까 싶다. 

 

이번 포스팅에서는 매서드, 즉 Java에서의 함수 정의 방법과 필요성에 대해 알아보려 한다. 그리고, 매서드만으로는 이번 포스팅의 내용이 그닥 많지는 않기 때문에, 클래스 선언과 객체 생성 시 메모리에서 일어나는 일에 대해서도 간략하게나마 다루어보려한다.

 

 

1. Java 클래스 매서드

 

매서드는 클래스 내의 함수다. 클래스가 특정 객체의 특성과 행위에 대해 정의된 것이며, 특성은 멤버변수, 행위는 매서드로 정의한다고 이전의 포스팅에서 언급했다. 정리하자면 매서드는 자신이 속한 클래스 객체의 행위를 정의하는 것이라 보면 된다. 

 

사람이라는 클래스를 한 번 가상으로 만들어보자. 누군가가 이 사람의 옆구리를 툭 찌르면, 그 사람은 찌르기에 대한 반응으로 몸을 움찔하면서 피할 것이다. 혹은 맛있는 음식 냄새가 나면 입에 침이 고이는 등, 외부의 환경에 대한 반응이 나타난다. 물론, 외부 반응이 있더라도 밖으로 나타나는 증상 없이 속으로 "아 맛있겠다" 하고 생각할수도 있고, 고통에 둔감한 사람이라면 걸어가다 전봇대에 머리를 박아도(?) 아무렇지 않은 사람이 있을 수 있다. 혹은 조용한, 아무 자극이 없는 환경에서 "나는 누구인가" 라는 철학적인 생각에 빠져 가만히 있는 경우도 있을 것이다.

 

필자가 하고 싶은 말은, 이러한 행위는 외부의 자극(입력) 및 반응(출력)으로 정의를 내릴 수 있고, 혹은 입력이 없거나 출력이 없는 행위도 객체가 충분히 가질 수 있다는 말이다. 즉, 클래스의 매서드는

 

-  입력에 따른 출력이 존재하거나

-  입력에 따른 출력이 존재하지 않거나

-  입력이 없어도 출력이 존재하거나

-  입력이 없고 출력이 없는

 

행위로 크게 카테고리를 나눌 수 있다. 매서드의 선언은 아래의 포맷을 가진다.

 

출력(반환)값_데이터타입 매서드명(입력값_데이터타입)

{

    매서드 호출 시 수행할 코드;

    return 출력(반환)값;

}

 

입력값이 존재하지 않는 행위에 대한 매서드를 작성할 경우, 입력값_데이터 타입을 생략하면 된다. 반대로, 출력값이 존재하지 않는 행위에 대한 매서드는 출력(반환)값_데이터타입을 생략하는 것이 아니라 void로 변경하며, return 뒤의 출력(반환)값출력(반환)값_데이터타입에 맞는 값으로 지정해주어야 한다.

 

좌 - 입력값과 출력값이 모두 존재하는 매서드,    우 - 입력값만 존재하는 매서드

 

좌 - 출력값만 존재하는 매서드,    우 - 입력값, 출력값이 모두 존재하지 않는 매서드

 

 

매서드에 의해 반환되는 값은, 변수에 저장하는 것도 가능하고, 직접 print 관련 함수의 인자로 사용할 수도 있다. 아래의 예시는 printf의 %c에 해당하는 인자를 MethodTest의 getting_first_char() 함수의 결과값으로 지정한 것이다.

 

 

 

지금까지 간략하게 클래스 내에서 정의하는 매서드의 유형와 사용법에 대해 알아보았다. 이제 클래스가 선언될 때, 그리고 매서드가 실행될 때 메모리에서 무슨 일이 벌어지는지 알아보려 한다.

 

 

 

2. 클래스와 메모리

 

변수, 배열 선언때와 마찬가지로, 클래스 역시 데이터를 저장하는 일종의 구조이기 때문에 선언, 실행, 객체 생성 등의 작업이 일어날 때 메모리에 관련 데이터가 저장된다. 지금부터 클래스의 선언, 생성, 인스턴스화, 매서드 실행 등의 과정을 통해 메모리에서 어떠한 변화가 일어나는지 확인해보려 한다.

 

 

(1) JVM 메모리 구조

 

Java는 클래스를 최소 단위로 동작하는 프로그램이다. 그래서 여러 개의 클래스나 매서드를, 혹은 하나의 클래스로부터 생성되는 여러 인스턴스를 다루기 위해, 종류별로 데이터를 저장할 필요가 있다. 이 때문에 JVM의 메모리 구조는 크게 아래와 같이 구분된다.

 

-  Method Area  :  클래스에 대한 데이터 정보 저장(멤버 변수 중 static이 붙은 변수 - Class Variable이라고도 한다)

-  Call Stack      :  클래스 내 매서드 정보 저장 (매서드 내에 정의된 별도의 변수 - Local Variable도 이곳에 저장된다)

-  Heap              :  클래스로부터 생성된 인스턴스에 대한 정보를 저장한다. 

 

 

(2) 클래스 정의

 

이전 포스팅에서 사용했던 SignalLight라는, 신호등 객체에 대해 정의한 클래스로 다시 돌아가보려한다. 단, 각 신호등마다 공통된 값을 가지는 radius_cm과 shape 변수는 인스턴스를 생성하더라도 함부로 변경되지 않게 하기 위해 변수타입 앞에 static을 추가하려 한다.

 

 

static이 붙은 멤버 변수를 클래스 변수(Class Variable)라고 한다. 이 클래스 변수는 정의하고자 하는 객체의 특성 중, 동일한 값을 가져야 하는 특성에 추가하여 사용한다. 위의 예시에서 신호등의 크기와 모양은 동일한 규격을 가져야하므로 static을 붙인것이다(빨간 신호등은 네모나고, 초록 신호등은 둥글면...)

 

이러한 클래스 변수들은 별도의 인스턴스 없이도 바로 접근이 가능하다. 아래의 예시를 보자.

 

 

 

클래스 변수는 인스턴스 없이도 어떻게 접근이 가능한 것일까? 이를 이해하기 위해서는 클래스 생성 시 메모리에서 일어나는 일에 대해 알아야 한다. 클래스가 작성되면 클래스 관련 데이터는 Method Area 부분에 저장되는데, 이 때 클래스 내의 모든 멤버변수(Member Variable)이 저장되는 것이 아니라 static이 붙은 클래스 변수만 저장된다.

 

 

 

따라서 클래스 변수는 인스턴스 생성 없이 바로 호출이 가능한 것이다. 위의 예시에서 매서드는 제외되어 있는데, 매서드 역시 반환타입 앞에 static을 붙여 클래스 매서드로 사용할 수 있으며, 클래스 변수와 동일하게 인스턴스 생성 없이 호출이 가능하다.

 

 

 

2. 인스턴스 생성

 

이제 클래스로부터 하나의 신호등 객체를 인스턴스화 해보자. 

 

 

클래스로부터 인스턴스가 생성되면, 생성된 인스턴스에 대한 정보는 메모리의 Heap 부분에 저장된다. Heap에 저장되는 정보는 멤버 변수 중 static이 붙지 않은 인스턴스 변수(Instance Variable)과 일반 매서드다. 위의 메모리 구조를 보면 알겠지만, 이 때문에 인스턴스 변수와 매서드는 반드시 인스턴스 생성이 선행되어야 하며, 접근을 위해 인스턴스 변수명을 반드시 명시해주어야 한다.

 

 

 

 

3. 클래스 매서드 호출 및 실행

 

클래스 내부에 정의된 매서드는 코드를 통해 호출되어야만 실행이 된다. 단순히 클래스 내부에 존재하는 것만으로는 아무 동작을 하지 않는다. 그럼, 클래스 내부에 존재하는 매서드는 메모리의 어떤 부분에 적재되어 호출과 실행이 이루어지는 것일까? 

 

메모리 구조를 SignalLight 외에 main 함수가 저장되어 있는 ClassEx 클래스까지 포함하여 다시 표시하면 아래와 같이 나타낼 수 있다.

 

 

 

오류가 없다는 가정아래, 컴파일과 실행을 진행하면, Jvm에 의해 Main 매서드가 먼저 호출된다. 클래스 내에 존재하는 매서드가 호출되면, 호출된 매서드는 메모리의 Call Stack(호출 스택)에 적재된다.

 

 

main 함수 내의 코드가 모두 정상 실행된다면,  Call Stack에서 main 함수는 사라진다. 그런데, 아래와 같이 main 함수가 종료되기 전에  다른 매서드를 실행하는 코드가 있다고 가정해보자.

 

 

Method Area와 Heap에는 클래스와 인스턴스에 대한 데이터가 이미 적재되어 있는 상태고, main 함수에서 Method Area의 SignalLight 클래스에서 print_info()를 호출했다. 아직 main 함수가 종료되지 않은 상태에서 print_info() 매서드가 호출되었기 때문에, print_info() 매서드는 Call Stack에 존재하는 main 함수의 위에 적재된다.

 

 

print_info() 매서드에 정의된대로 신호등에 대한 정보 출력이 완료되면, Call Stack에서 print_info() 매서드는 제거된다. 또한 print_info() 매서드 종료와 동시에 main 함수도 종료되므로 Call Stack은 완전히 비워진다. 

 

매서드 안에 매서드가 계속 반복되면 어떻게 될까? A 매서드 안에 A 매서드를 실행하는 등의 변태같은(?) 행위를 한다면 말이다. 이 경우, 처음 호출된 A 매서드는 영원이 끝나지 않고 새로운 A 매서드를 호출하여 Call Stack에 쌓아올리기 때문에 Call Stack에 사용할 수 있는 공간이 사라지게 되면서 에러가 발생한다. 이를 Stack Overflow라고 한다.

 

 

위의 예시처럼 자기 자신을 호출하는 함수를 재귀함수(Recursive Function)이라고 하는데, 재귀함수의 경우, 별도의 조건 없이 사용하는 경우 위와 같이 StackOverflowError가 발생하게 된다.

 

 


 

이번 포스팅에서는 클래스 매서드 작성법 및 코드 실행 시 메모리에서 일어나는 일에 대해 종합적으로 살펴보았다. 메모리 상태 부분은 필자가 순서 상관없이 뒤죽박죽 설명을 진행했는데, 실제 코드 실행 시 일어나는 일을 정확히 정리하자면 아래와 같다.

 

 

1. 코드 작성 완료 및 컴파일/실행

2. 클래스 단위로 Method Area에 클래스 데이터 적재

3. main 함수 실행

   3-1. 클래스 인스턴스화 코드 존재 시, Heap에 인스턴스 데이터 적재

   3-2. 매서드 실행 시, Method Area와 Heap에 정의된 매서드를 Call Stack에 적재

           (단, 매서드 내에서 정의된 변수는 Call Stack에 적재됨)

   3-3. 매서드가 종료되면 Call Stack에서 해당 매서드는 종료.

   3-4. 매서드 종료 전 다른 매서드가 호출되면, 현재 Call Stack위에 호출된 매서드를 적재.

   3-5. 모든 매서드 및 main 함수 종료 시 Call Stack 내 모든 정보를 삭제

4. 프로그램 종료

 

 

위의 절차를 참고하면, 클래스 매서드(static이 붙은 매서드)는 인스턴스 변수/매서드 호출이 불가능하다. 인스턴스 변수/매서드는 반드시 인스턴스화가 진행되어야만 메모리에 적재되는데, 클래스 매서드는 인스턴스화 없이도 존재할 수 있기 때문이다. 반대로 인스턴스 매서드는 클래스 변수와 클래스 매서드의 호출에 제약이 없다.

 

 

다음 포스팅에서는 매서드 오버로딩에 대해 알아보려 한다.

 

 

 

Fin.

 

 

반응형

댓글