새소식

java/java 필기

참조형 배열 + 변수의 생성 시점과 자바 메모리 모델

  • -

두 개념은 서로 연관이 있지는 않지만,

확실하게 이해하고 있지 않던 개념들을 이해하고자 글을 쓰게 되었습니다.

 

 

1. 참조형 배열

우선 자바에서 기본 타입을 사용해서 배열을 생성시엔,

배열의 요소(인스턴스)에는 값들이 저장되고, 배열 변수에는 값들의 모음(인스턴스에 접근할 수 있는 참조값)이 저장이된다.

 

하지만 참조 타입을 이용할 경우에는 위와 다르다.

1. 배열의 값에는 인스턴스 자체를 저장하는 것이 아니고 그 참조값(인스턴스의 주소)을 저장한다.

2. 배열 변수에는 첫번째 요소의 인스턴스 참조값이 저장된다.

3. 참조형 배열은 선언한 타입으로만 값을 넣을 수 있다.

 

Hyunjin[] arr = new Hyunjin[3]; // 자바의 배열은 크기를 초기화 해줘야 하고, 변경은 어려움
// 위와 같은 사용자 정의 자료형이자 참조형 타입 'Hyunjin'의 배열이 있을때
// 배열의 요소로는 Hyunjin 인스턴스만 저장할 수 있고, 다른 자료형은 저장할 수 없다.

<!--위의 코드는 배열만 생성했지 Hyunjin 객체의 인스턴스는 생성되지 않았음->
arr[0] = new Hyunjin();

 

 

 

2. 변수의 생성 시점

이 파트는 자바의 메모리 모델과 연관 지을 수 있다.

 

우선 변수와 관련하여

정적 변수(클래스 변수)와 인스턴스 변수의 선언 위치는

클래스 영역으로 동일하나

실행 시점과 관련해서는

정적 변수는 클래스가 메모리에 올라갈 때 실행되고,

인스턴스 변수는 인스턴스가 생성되었을 때 실행이 된다.

 

정적 변수는 클래스가 메모리에 올라갈 때 실행되기 때문에, 인스턴스 생성 전에도 사용할 수 있다.

ex) 클래스명.클래스변수(메서드)

 

프로그램을 실행하면 메모리에 자원들이 쌓이게 되는데

자바 프로그램은 JVM에 의해 실행되기 때문에 어떤 운영체제에서 사용하던 동일한 결과이나

JVM을 통한 추가적인 단계로 인해 속도는 조금 늦을 수 있다.

또한 JVM은 운영체제로 부터 메모리를 할당 받고, 여러 영역으로 나누어 메모리를 관리한다.

 

 

자바의 메모리모델은 여러가지로 구성이 되어있는데

크게 stack / heap / method area로 구분할 수 있다.

 

2-1. 스택 메모리

스택 자료구조의 방식으로 관리하는 메모리구조이다.

스택 메모리에는 스택 프레임이 push되고 pop된다.

하나의 메서드가 호출될때마다 push/pop의 과정을 거치게 된다

 

ex) 메인 메서드가 실행되면

하나의 스택 프레임이 생성되고, 메인 메서드에서 다른 정적 메서드를 호출하면

두번째의 스택 프레임이 생성된다.

 

(그림 첨부)

 

메서드 영역에 선언된 변수 또한 스택 프레임단위로 생성하고 소멸한다.

스택 프레임은 3가지로 나눌 수 있는데

1. Local variables arrays: 지역 변수 저장 (길이는 컴파일시 정해짐)

2. Operand stack: 명령어들을 실행시키며 중간 연산 결과를 일시적으로 저장하는 스택

(상수, 리터럴값을 메모리에 일시적으로 저장)

3. Frame stack: 메서드와 관련된 그 외 정보

로 구성된다.

 

public static void main(String[] args){

	int a = 1;
    int sum = plus(3,5);
}

public static void change(int a, int b){

	int new_a = a;
    int new_b = b;
    int sum = new_a + new_b;
    
    return sum
}

 

 

위와 같이 코드가 정의되었을 때 메인 메서드가 호출되면

스택에 메인 메서드와 연관된 스택 프레임을 push 하고

Local variables arrays에는 메인 메서드의 매개변수인 args, 지역변수인 a와 sum이 적재된다.

 

int a = 1; 이 실행되면 Operand stack에 리터럴 값 1을 추가한다.

그 후 Operand stack이 pop되어 Local variables arrays 1번 인덱스(a)에 1(a값)이 저장된다.

 

int sum = plus(3,5);이 실행되면 3,5가 Operand stack에 적재되고

plus 메서드가 실행되며 Operand stack의 값들을 pop하고

실행흐름이 메인메서드에서 plus메서드로 변환된다.

 

스택 메모리에는 plus메서드와 관련된 새로운 스택 프레임이 push된다.

(이 때 매개변수로 전달된 값들은 Local variables arrays 에 0번/1번 인덱스로 저장된다.)

 

plus 메서드의 실행이 종료되어 반환된 값은

메인 메서드의 Operand stack에 push된다.

 

메인 메서드가 종료되면 스택 프레임을 pop하고

모든 과정을 마치면 프로그램이 종료된다.

 

=> 메서드의 매개변수도 별도의 메모리 공간을 가지고 있으며,

메서드 내부의 지역변수의 수명주기는 스택 프레임 단위이다.

(스택 프레임이 pop되면 지역변수도 해제된다.)

 

+ 스택은 유한한 공간이기 때문에 가득 차면 stack overflow가 발생시킨다.

 

+

public static void main(String[] args){

	int a = 1;
    int b = 10; 
    change(a,b);
}

public static void change(int a, int b){

	int temp = a;
    a = b;
    b = temp;
    
}

 

메인 메서드와 change메서드는 메모리 공간이 다르다

(서로 다른 스택 프레임)

 

change 메서드에서 a와b값이 바뀌더라도 change 메서드의 스택 프레임 내에서 해당하기 때문에 

메인 메서드의 a,b값은 변경되지 않는다.

 

change의 매개변수가 리터럴이 아닌 인스턴스여도 결과는 동일하다.

참조값도 리터럴 값과 똑같은 '값'이기 때문이다.

그리고 그 값은 스택 프레임 내부에서 교환되기 때문

변경하려면 메인 메서드 내에서 변경해야 한다. 

 

+

 

public class Person{
	public int age1 = 10;
    public int age2 = 20;
}

public static void main(String[] args){
	Person person = new Person();
    change(person);
}

public static void change(Person person){
	int temp = person.age2;
    person.age2 = person.age1;
    person.age1 = temp;
}

 

 

Person 클래스의 인스턴스 변수는 힙에서 관리되기 때문에

메서드의 지역 변수의 스택 프레임 생명주기와는 다르다.

 

위 코드는 change 메서드에서 Person 클래스의 인스턴스 변수에 접근해서 교환하기 때문에

age1 과 age2의 값을 main 메서드에서 출력하면 교환된 값이 출력된다.

 

 

 

 

2-2 . 힙

new 키워드로 생성된 인스턴스나, 문자열 상수가 저장된다.

ex) new int[3]; // 배열 생성

new String(); // 문자열 생성

new Person(); // 참조 자료형 인스턴스 생성

 

인스턴스의 주소값(=참조값)을 참조형 변수에 저장하고, 참조값을 이용하여

인스턴스가 저장된 힙 공간에 접근한다.

 

+ 힙 메모리에는 참조 자료형에 의해 참조되지 않는 인스턴스나, 문자열 상수는

가비지 컬렉터가 수집한다.(스케줄링에 의해 메모리에서 해제됨)

참조되지 않는다 = 인스턴스에 접근할 방법이 없다 => 인스턴스가 필요없음, 접근할 이유가 없음, 사용하지 않음

 

 

 

2-3. 메서드 영역

JVM이 시작될 때 생성된다.

JVM이 자바 코드를 실행하기 위한 주요한 정보들을 저장한다

ex) 런타임 상수 풀, 클래스 정보, 필드 정보(멤버 변수), 메서드 정보, 정적 메서드 정보, 인터페이스 정보, 생성자 정보, 정적 변수 정보..

 

정적 변수는 메서드 영역에 저장된다.

단, 정적 변수가 참조하는 인스턴스나 문자열 상수는 heap 메모리에 저장된다.

 

정수, 실수형 리터럴은 메서드 영역에서 관리한다.

 

+ 정적 변수에 저장되는 것이 인스턴스라면 heap, 수 리터럴이면 런타임 상수 풀(메서드 영역), 문자열은 문자열 상수 풀(힙)

정적 변수와 (인스턴스, 문자열 상수)는 서로 다른 곳에 저장됨

 

정보에 도움을 얻은 곳 & 출처: 

 

 

 

'java > java 필기' 카테고리의 다른 글

리터럴과 접미사 그리고 형변환  (0) 2024.01.19
Contents

포스팅 주소를 복사했습니다

이 글이 도움이 되었다면 공감 부탁드립니다.