Java공부(코딩)

코딩초보의 자바(Java) 공부 10일차 { 메모리구조와 static }

동곤일상 2024. 12. 15. 17:36
반응형

\

오늘 다룰 내용은 조금 복잡하기에
김영한 님의 자바강의를
기반으로 작성해볼게요!!

 


1) 자바 메모리 구조

자바의 메모리 구조는 크게 메서드 영역, 스택 영역, 영역 3개로 나눌 있다.

 

**메서드 영역**:

클래스 정보를 보관한다.

클래스 정보가 커피머신이다.

 

**스택 영역**:

실제 프로그램이 실행되는 영역이다.

메서드를 실행할 마다 하나씩 쌓인다.

 

** 영역**:

객체(인스턴스) 생성되는 영역이다.

`new` 명령어를 사용하면 영역을 사용한다.

ex)커피머신으로 만든 커피가 존재하는 공간이다.

참고로 배열도 영역에 생성된다.(참조형)

 


메서드영역)

메서드영역은 프로그램에 필요한

공통 데이터를 모두 관리한다.

 

클래스 정보:

클래스의 실행 코드(바이트 코드), 필드, 메서드와 생성자 코드등

모든 실행 코드가 존재한다.

 

static 영역: `

static` 변수들을 보관한다.

뒤에서 자세히 설명한다.

 

런타임 상수 :

프로그램을 실행하는데 필요한 공통 리터럴 상수를 보관한다.

예를 들어서 프로그램에`"hello"` 라는 리터럴 문자가 있으면

이런 문자를 공통으로 묶어서 관리한다.

외에도 프로그램을 효율적으로 관리하기 위한 상수들을 관리

. (참고로 문자열을 다루는 문자열 풀은 자바 7부터 영역으로 이동했다.)

 


스택 영역(Stack Area)

: 자바 실행 , 하나의 실행 스택이 생성된다.

스택 프레임은 지역 변수, 중간 연산 결과,

메서드 호출 정보 등을 포함한다.

 

(쓰레드 1개 당 1개의 스택이 생성

현재는 1개의 쓰레드만 다루고있으므로

스택이 하나만 생성 됨)

(멀티 쓰레드에 관해 공부해보시길!!)


힙 영역(Heap Area)**

객체(인스턴스) 배열이 생성되는 영역이다.

가비지 컬렉션(GC) 이루어지는 주요 영역이며,

이상 참조되지 않는 객체는 GC 의해 제거된다.


메서드 코드는 메서드 영역에서 !

자바에서 특정 클래스로 100개의 인스턴스를 생성하면, 메모리에 100개의 인스턴스가 생긴다.

각각의 인스턴스는내부에 변수와 메서드를 가진다.

같은 클래스로 부터 생성된 객체라도, 인스턴스 내부의 변수 값은 서로 다를 있지만,

메서드는 공통된 코드를 공유한다.

따라서 객체가 생성될 , 인스턴스 변수에는 메모리가 할당되지만, 메서드에 대한

새로운 메모리 할당은 없다.

메서드는 메서드 영역에서 공통으로 관리되고 실행된다.

정리하면 인스턴스의 메서드를 호출하면 실제로는 메서드 영역에 있는 코드를 불러서 수행한다.

 


 

2) 스택영역

스택영역이란?

예를 들어  그릇에

음식을 쌓는다고 생각해보자

맨위에서부터 꺼내게 될 것이다.

 

이게 스택구조와 비슷하다

(LIFO(LastInFirstOut))

후입선출 !!

1 -> 2 -> 3 (으로 넣으면)

3 -> 2 -> 1 (으로 나오게 됨)


스택영역과 힙영역)

package memory;

public class Data {

String name;

  Data(String name){

this.name =name;}}

package memory;

 

public class JavaMemoryMain1 {

public static void main(String[] args) {

System.out.println("Main method Start");

Method1();

System.out.println("Main Method End");

}

static void Method1() {

System.out.println("Method1 start");

Data data1 = new Data("힙 영역");

Method2(data1);

System.out.println("method1 End");

}

static void Method2(Data data2) {

System.out.println("Method2 Start");

System.out.println("data : "+data2.name);

System.out.println("Method2 End");}

}

////////////////////////////

출력

Main method Start

Method1 start

Method2 Start

data : 힙 영역

Method2 End

method1 End // 이 때 data인스턴스가  힙 영역에서 사라짐(아무도참조하지 않기에)

Main Method End

 

1)main Method 실행 -> method1 호출

 

2)Method1 에서 Data 클래스의 인스턴스 생성

 

3) method2에 인스턴스 참조값 넘기기

 

4)method2 실행

 

5) method 2 종료

 

6) method1 종료--> data인스턴스 삭제

(아무도 참조하지 않는 객체는 삭제)

(GC에 의해)

 

method1 종료 직후의 모습

 스택영역                         힙영역

 

7) mainMethod 종료


3)Static변수

`static` 키워드는 주로 멤버 변수와 메서드에 사용된다.

특정 클래스에서 공용으로사용하기 위해 있는 것!!
예제를 통해 static변수를 사용한 것과

ㅇ사용하지않은것의 차이를 보자

public class Counter {
    public  int count;
}
public class Data2 {
    public String name;


    public Data2(String name , Counter counter){
        this.name = name;
        counter.count++;
    }
}
public class DataCountMain2 {
    public static void main(String[] args) {
        Counter counter = new Counter();
        Data2 data1 = new Data2("a",counter);
        System.out.println("a count = "+counter.count);


        Data2 data2 = new Data2("b",counter);
        System.out.println("b count = "+counter.count);

        Data2 data3 = new Data2("c",counter);
        System.out.println("c count = "+counter.count);

 

공용 count를 만들기 위해서는 이와같이 만들어야한다

counter클래스를 따로 만든 후

그 인스턴스를 모두가 참조하게 끔 만들어야 함.

메모리낭비가 심함 !

이와같이 힙영역에서 관리해야 할 인스턴스가 많음.

 

 

static변수를 사용해 이 점을 해결할 수 있음

(공용변수)


static 변수 사용

public class Data2 {

String name;

static int count;

 

Data2(String name){

this.name =name;

count++;}}

public static void main(String[] args) {

Data2 data1 = new Data2("하");

Data2 data2 = new Data2("하하");

Data2 data3 = new Data2("하하하");

System.out.println(Data2.count);}

static변수는 이런식으로

메서드영역 안에 static영역에서 관리 된다.

 

정리

`static` 변수는 쉽게 이야기해서 클래스인 커피머신 특별히 관리하는 변수이다.

커피머신은 1개이므로 클래스 변수도 하나만 존재한다.

반면에 인스턴스 변수는 커피 인스턴스의 만큼 존재한다.

(커피머신 하나로 커피를 하나만들면 사라지지 않는다)

 

용어정리)

멤버 변수(필드) 종류**

인스턴스 변수:

`static` 이 붙지 않은 멤버 변수, ) `name`

`static` 붙지 않은 멤버 변수는 인스턴스를 생성해야 사용할 있고, 인스턴스에 소속되어 있다

. 따라서 인스턴스 변수라 한다.

인스턴스 변수는 인스턴스를 만들 마다 새로 만들어진다.

 

클래스 변수:

`static` 이 붙은 멤버 변수, ) `count`

클래스 변수, 정적 변수, `static` 변수등으로 부른다.

**용어를 모두 사용하니 주의하자**

`static` 붙은 멤버 변수는 인스턴스와 무관하게 클래스에 바로 접근해서 사용할 있고,

클래스 자체에 소속되어 있다.

따라서 클래스 변수라 한다.

클래스 변수는 자바 프로그램을 시작할 1개가 만들어진다.

인스턴스와는 다르게 보통 여러곳에서 공유하는 목적으로 사용된다.

 

지역 변수(매개변수 포함)

: 지역 변수는 스택 영역에 있는 스택 프레임 안에 보관된다.

메서드가 종료되면 스택 프레임도 제거

 이때 해당 스택 프레임에 포함된 지역 변수도 함께 제거된다.

따라서 지역 변수는 생존 주기가 짧다.

 

인스턴스 변수:

인스턴스에 있는 멤버 변수를 인스턴스 변수라 한다.

인스턴스 변수는 영역 사용한다.

영역은 GC(가비지 컬렉션) 발생하기 전까지는 생존하기 때문에

보통 지역 변수보다 생존 주기가 길다.

 

클래스 변수(정적변수):

클래스 변수는 메서드 영역의 static 영역에 보관되는 변수이다.

메서드 영역은 프로그램 전체에서사용하는 공용 공간이다.

클래스 변수는 해당 클래스가 JVM 로딩 되는 순간 생성된다.

그리고 JVM 종료될때 까지 생명주기가 이어진다.

따라서 가장 생명주기를 가진다.

 

정적변수 접근법 )

정적변수는 인스턴스를 생성하지않고

클래스를 통해 접근하는 방식을 추천!

(정적변수는 클래스에서 공용으로관리)

 


static 메서드 )

정적 메서드 사용법

`static` 메서드는 `static` 사용할 있다.

클래스 내부의 기능을 사용할 , 정적 메서드는 `static` 붙은

정적 메서드나 정적 변수만 사용할 .

 

클래스 내부의 기능을 사용할 , 정적 메서드는 인스턴스 변수나,

인스턴스 메서드를 사용할 없다.

반대로 모든 곳에서 `static` 호출할 있다.

정적 메서드는 공용 기능이다.

따라서 접근 제어자만 허락한다면 클래스를 통해 모든 곳에서

`static` 호출할 있다.

 

멤버 메서드의 종류

**인스턴스 메서드**:

`static` 붙지 않은 멤버 메서드

 

**클래스 메서드**:

`static` 붙은 멤버 메서드

클래스 메서드, 정적 메서드, `static` 메서드등으로 부른다.

`static` 붙지 않은 멤버 메서드는 인스턴스를 생성해야 사용 있고, 인스턴스에 소속되어 있다.

따라서 인스턴스 메서드라 한다.

`static` 붙은 멤버 메서드는 인스턴스와 무관하게

클래스에 바로 접근해서 사용할 있고, 클래스 자체에 소속되어 있다.

따라서 클래스 메서드라 한다.

 

 

예시를 한번보자

package static2;

public class DecoData {

    private int instanceValue;
    private static int staticValue;

    public static void staticCall(){

        //instanceValue++; //인스턴스변수 접근 compile error
        //정적메서드가 인스턴스 기능 사용할수 없는 이유
        // 참조값이 없는 인스턴스기능을 사용불가 참조값을 직접 전달하면 가능
        //instanceMethod(); //인스턴스 메서드 접근 compile error
        staticValue++; //정적변수접근
        staticMethod(); //정적메서드 접근 //static메서드는 static만 사용가능
    }
    public void instanceCall(){

        instanceValue++; //인스턴스변수 접근
        instanceMethod(); //인스턴스 메서드 접근

        staticValue++; //정적변수접근
        staticMethod(); //정적메서드 접근
    }

    public static void staticCall(DecoData data){ //오버라이딩
        //참조값으로 접근 가능
        data.instanceValue++;
        data.instanceMethod();
    }
    private void instanceMethod(){
        System.out.println("instanceValue = "+instanceValue);
    }
    private static void staticMethod(){
        System.out.println("staticValue = "+staticValue);
    }
}

 

import static static2.DecoData.*  이란?

static2패키지의 DecoData 클래스를

import하지않고 사용하는 방법 중 하나이다.

(정적 변수, 정적메서드만 사용가능)

package static2;

//import static static2.DecoData.*;

public class DecoDataMain1 {
    public static void main(String[] args) {
        System.out.println("1.정적 호출");
        DecoData.staticCall();

        System.out.println("2.인스턴스호출");
        DecoData data1 = new DecoData();
        data1.instanceCall();

        System.out.println("3.인스턴스호출2");
        DecoData data2 = new DecoData();
        data2.instanceCall();   //새로운 인스턴스 생성으로 인해 (힙영역)instanceValue는 다른 참조값을 가지게 됨 and
                                    // staticValue는 정적변수이므로 (메서드영역)
                                    // 인스턴스생성(참조값이 달라져도)과는 관계가 없음

        System.out.println("4.참조값을 인수로받는 정적메서드");
        DecoData.staticCall(data1); //정적메서드는 클래스에서 바로불러올수있음 인스턴스를 안 만들어도부를수 있음
                                    //참조값으로 정적메서드 접근 가능
        //추가
        //인스턴스를 통한 접근
        DecoData data3 = new DecoData();
        System.out.println("인스턴스로 정적메서드 접근 (추천 안함) 헷갈림");
        data3.staticCall(); //인스턴스로접근하는것은 옳지 않은 방법 (클래스로 직접 접근하자 정적메서드 정적변수는 메서드영역임)
    }
}

 

결과)

1.정적 호출
staticValue = 1


2.인스턴스호출(data1)
instanceValue = 1
staticValue = 2


3.인스턴스호출(data2)
instanceValue = 1
staticValue = 3


4.참조값을 인수로받는 정적메서드(data1)
instanceValue = 2

 

5.인스턴스로 정적메서드 접근 (추천 안함) 헷갈림
staticValue = 4

 


`instanceValue` 인스턴스 변수이다.

 

`staticValue` 정적 변수(클래스 변수)이다.

 

`instanceMethod()` 인스턴스 메서드이다.

 

`staticMethod()` 정적 메서드(클래스 메서드)이다.

 

 

 


main() 메서드는 정적 메서드

인스턴스 생성 없이 실행하는 가장 대표적인 메서드가 바로 `main()` 메서드이다.

`main()` 메서드는 프로그램을 시작하는 시작점이 되는데,

생각해보면 객체를 생성하지 않아도 `main()` 메서드가 작동했다.

이것은 `main()` 메서드가 `static` 이기 때문.

 

main() 메서드와 static 메서드 호출 **

 

public class ValueDataMain {

public static void main(String[] args) {

ValueData valueData = new ValueData();

add(valueData);}

 

static void add(ValueData valueData) {

valueData.value++;

System.out.println("숫자 증가 value=" + valueData.value);}

}

 

main메서드는 static메서드만 사용가능

나머지는 인스턴스를 생성해서 호출

물론 정확히 말하자면 정적 메서드는 같은 클래스 내부에서 정적 메서드만 호출할 있다.

따라서 정적 메서드인`main()` 메서드가 같은 클래스에서 호출하는

메서드도 정적 메서드로 선언해서 사용했다.


예제)

다음 기능을 제공하는 배열용 수학 유틸리티 클래스(`MathArrayUtils` ) 만드세요.

`sum(int[] array)` : 배열의 모든 요소를 더하여 합계를 반환합니다.

`average(int[] array)` : 배열의 모든 요소의 평균값을 계산합니다.

(평균값은 소수점으로 나올 수 있어야겠죠 ?)

`min(int[] array)` : 배열에서 최소값을 찾습니다.

`max(int[] array)` : 배열에서 최대값을 찾습니다.

 

**요구사항**

`MathArrayUtils` 객체를 생성하지 않고 사용해야 합니다.

누군가 실수로 `MathArrayUtils` 인스턴스를 생성하지 못하게 막으세요.

실행 코드에 `static import` 사용해도 됩니다.

 

public class MathUtil {

 

private MathUtil() {

//인스턴스 생성 막기위해 private

}

 

public static int sum(int[] array) {

int total=0;

for (int i : array) {

total += i;

}return total; }

 

public static double avg(int[] array) {

double total=0;

for (int i = 0; i < array.length; i++) {

total+=array[i];

}return total/array.length;}

 

public static int min(int[] array) {

int min = array[0];

for (int i : array) {

if(min > i) {

min = i;}}return min;}

 

public static int max(int[] array) {

int max = array[0];

for (int i : array) {

if(max < i) {

max = i;}}

return max;}

}

생성자 private로 제한(인스턴스 생성 불가능)

 

메인코드 와 출력 예시

public class MathMain {

 

public static void main(String[] args) {

// new MathUtil();

Scanner scan = new Scanner(System.in);

System.out.print("배열의 크기 입력 : ");

int[] num = new int[scan.nextInt()];

 

int i=0;

while(i < num.length) {

System.out.print((i+1)+"번째 숫자 입력 : ");

int number = scan.nextInt();

num[i] = number;

i++;

}

 

int sum = MathUtil.sum(num);

System.out.println("sum : "+sum);

 

double avg = MathUtil.avg(num);

System.out.println("avg = "+avg);

 

int min = MathUtil.min(num);

System.out.println("min : "+min);

 

int max = MathUtil.max(num);

System.out.println("max : "+max);

}}

/////////////////////////////

출력

 

배열의 크기 입력 : 4

1번째 숫자 입력 : 62

2번째 숫자 입력 : 45

3번째 숫자 입력 : 10

4번째 숫자 입력 : 5

sum : 122

avg = 30.5

min : 5

max : 62

 

참고 : 김영한의 자바 (기본편)

https://inf.run/YQbQJ