7-1. 자바 메모리 구조
자바의 메모리 구조는 크게 메서드 영역, 스택 영역, 힙 영역 3개로 나뉨
- 메서드 영역: 클래스 정보를 보관. 이 클래스 정보가 붕어빵 틀이다.
- 스택 영역: 실제 프로그램이 실행되는 영역. 메서드를 실행할 때 마다 하나씩 쌓인다.
- 힙 영역: 객체(인스턴스)가 생성되는 영역. new 명령어를 사용하면 이 영역을 사용.
붕어빵 틀로부터 생성된 붕어빵이 존재하는 공간. 참고로 배열도 이 영역에 생성된다.
- 메서드 영역(Method Area): 메서드 영역은 프로그램을 실행하는데 필요한 공통 데이터를 관리.
이 영역은 프로그램의 모든 영역에서 공유.- 클래스 정보: 클래스의 실행 코드(바이트 코드), 필드, 메서드와 생성자 코드 등 모든 실행 코드가 존재.
- static 영역: static 변수들을 보관. 뒤에서 자세히 설명.
- 런타임 상수 풀: 프로그램을 실행하는데 필요한 공통 리터럴 상수를 보관.
예시로, 프로그램에 "hello" 라는 리터럴 문자가 있으면 이런 문자를 공통으로 묶어서 관리.
이 외에도 프로그램을 효율적으로 관리하기 위한 상수들을 관리.
(참고로 문자열을 다루는 문자열 풀은 자바 7부터 힙 영역으로 이동.)
- 스택 영역(Stack Area): 자바 실행 시, 하나의 실행 스택이 생성.
각 스택 프레임은 지역 변수, 중간 연산 결과, 메서드 호출 정보 등을 포함한다.- 스택 프레임: 스택 영역에 쌓이는 네모 박스가 하나의 스택 프레임.
메서드를 호출할 때 마다 하나의 스택 프레임이 쌓이고, 메서드가 종료되면 해당 스택 프레임이 제거됨. - 각 쓰레드 별로 하나의 실행 스택이 생성. 따라서 쓰레드 수 만큼 스택 영역이 생성.
쓰레드는 멀티 쓰레드를 학습해야 이해 가능
- 스택 프레임: 스택 영역에 쌓이는 네모 박스가 하나의 스택 프레임.
- 힙 영역(Heap Area): 객체(인스턴스)와 배열이 생성되는 영역.
가비지 컬렉션(GC)이 이루어지는 주요 영역이며, 더 이상 참조되지 않는 객체는 GC에 의해 제거됨
메서드 코드는 메서드 영역에
- 자바에서 특정 클래스로 100개의 인스턴스를 생성하면,
힙 메모리에 100개의 인스턴스가 생기고 각각의 인스턴스는 내부에 변수와 메서드를 가진다. - 같은 클래스로 부터 생성된 객체라도, 인스턴스 내부의 변수 값은 서로 다를 수 있지만,
메서드는 공통된 코드를 공유한다. - 따라서 객체가 생성될 때, 인스턴스 변수에는 메모리가 할당되지만, 메서드에 대한 새로운 메모리 할당은 없다.
- 메서드는 메서드 영역에서 공통으로 관리되고 실행된다.
- 정리하면 인스턴스의 메서드를 호출하면 실제로는 메서드 영역에 있는 코드를 불러서 수행한다.
7-2. 스택과 큐 자료 구조
스택(Stack)이라는 자료 구조를 알아보자
자료 구조: 데이터를 어떤 식으로 보관하고 관리하는지에 대한 구조
스택 구조 => 후입 선출(LIFO, Last In First Out)
프로그램 실행과 메서드 호출에는 스택 구조가 적합
큐(Queue) => 선입 선출(FIFO, First In First Out)
선착순 프로그램에 적합
7-3. 스택 영역
JavaMemoryMain1.java
package memory;
public class JavaMemoryMain1 {
public static void main(String[] args) {
System.out.println("main start");
method1(10);
System.out.println("main end");
}
static void method1(int m1) {
System.out.println("method1 start");
int cal = m1 * 2;
method2(cal);
System.out.println("method1 end");
}
static void method2(int m2) {
System.out.println("method2 start");
System.out.println("method2 end");
}
}
- 자바는 스택 영역을 사용해서 메서드 호출과 지역 변수(매개변수 포함)를 관리한다.
- 메서드를 계속 호출하면 스택 프레임이 계속 쌓인다.
- 지역 변수(매개변수 포함)는 스택 영역에서 관리한다.
- 스택 프레임이 종료되면 지역 변수도 함께 제거된다.
- 스택 프레임이 모두 제거되면 프로그램도 종료된다
7-4. 스택 영역과 힙 영역
Data.java
package memory;
public class Data {
private int value;
public Data(int value) {
this.value = value;
}
public int getValue() {
return value;
}
}
JavaMemoryMain2.java
package memory;
public class JavaMemoryMain2 {
public static void main(String[] args) {
System.out.println("main start");
method1();
System.out.println("main end");
}
static void method1() {
System.out.println("method1 start");
Data data1 = new Data(10);
method2(data1);
System.out.println("method1 end");
}
static void method2(Data data2) {
System.out.println("method2 start");
System.out.println("data.value=" + data2.getValue());
System.out.println("method2 end");
}
}
- main() ▶ method1() ▶ method2() 순서로 호출하는 단순한 코드
- method1() 에서 method2() 를 호출할 때 매개변수에 Data 인스턴스의 참조값을 전달
- method1() 이 종료된 직후, method1() 의 스택 프레임이 제거되고 지역 변수 data1 도 함께 제거
- 이제 x001 참조값을 가진 Data 인스턴스를 참조하는 곳이 더는 없는 프로그램에서 더는 사용하지 않는 객체가 됨.
- GC(가비지 컬렉션)은 이렇게 참조가 모두 사라진 인스턴스를 찾아서 메모리에서 제거
※ 지역 변수는 스택 영역에, 객체(인스턴스)는 힙 영역에 관리되는 것을 확인
7-5. static 변수1
static 키워드는 주로 멤버 변수와 메서드에 사용
멤버 변수에 static 키워드가 왜 필요?
Data1.java (인스턴스 내부에 카운트 저장)
package static1;
public class Data1 {
public String name;
public int count;
public Data1(String name) {
this.name = name;
count++;
}
}
- 객체가 생성될 때마다 생성자를 통해 인스턴스의 멤버 변수 count 값 증가
- 예제를 단순하게 만들기 위해 필드에 public 사용
DataCountMain1.java
package static1;
public class DataCountMain1 {
public static void main(String[] args) {
Data1 data1 = new Data1("A");
System.out.println("A count=" + data1.count);
Data1 data2 = new Data1("B");
System.out.println("B count=" + data2.count);
Data1 data3 = new Data1("C");
System.out.println("C count=" + data3.count);
}
}
- 객체를 생성할 때 마다 Data1 인스턴스는 새로 만들어지고 인스턴스에 포함된 count 변수도 새로 만들어짐
- 인스턴스에 사용되는 멤버 변수 count 값은 인스턴스끼리 서로 공유되지 않음
- 문제를 해결하려면 변수를 서로 공유해야 함
Counter.java (외부 인스턴스에 카운트 저장)
package static1;
public class Counter {
public int count;
}
Data2.java
package static1;
public class Data2 {
public String name;
public Data2(String name, Counter counter) {
this.name = name;
counter.count++;
}
}
DataCountMain2.java
package static1;
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("A", counter);
System.out.println("B count=" + counter.count);
Data2 data3 = new Data2("A", counter);
System.out.println("C count=" + counter.count);
}
}
- Data2 클래스와 관련된 일인데, Counter라는 별도의 클래스 추가 사용
- 생성자의 매개변수도 추가되고, 생성자가 복잡해짐.
7-6. static 변수2
특정 클래스에서 공용으로 사용할 수 있는 변수 만들기
Data3.java (static 변수 사용)
package static1;
public class Data3 {
public String name;
public static int count; // static
public Data3(String name) {
this.name = name;
count++;
}
}
- 멤버 변수 앞에 static을 붙이면, static 변수 = 정적 변수 = 클래스 변수 라 함
DataCountMain3.java
package static1;
public class DataCountMain3 {
public static void main(String[] args) {
Data3 data1 = new Data3("A");
System.out.println("A count=" + Data3.count);
Data3 data2 = new Data3("B");
System.out.println("B count=" + Data3.count);
Data3 data3 = new Data3("C");
System.out.println("C count=" + Data3.count);
}
}
- count 정적 변수에 접근하는 방법.
- Data3.count와 같이 클래스명에 .dot을 사용해 클래스에 직접 접근하는 것 같음.
- static이 붙은 멤버 변수는 메서드 영역에서 관리
※ static 변수는 예로, 클래스인 붕어빵 틀이 특별히 관리하는 변수
(붕어빵 틀 = 1, 클래스 변수 = 1), (붕어빵 = n, 인스턴스 변수 = n)
7-7. static 변수3
멤버 변수(필드)의 종류 (용어 정리)
- 인스턴스 변수
- static이 붙지 않은 멤버 변수
- 인스턴스를 생성해야 사용 가능, 인스턴스에 소속
- 인스턴스를 만들 때마다 새로 생성
- 클래스 변수 (= 정적 변수, static 변수)
- static이 붙은 멤버 변수
- 인스턴스와 무관하게 클래스를 통해 바로 접근해서 사용 가능, 클래스 자체에 소속
- 자바 프로그램을 시작할 때 딱 1개가 생성, 여러 곳에서 공유할 목적
변수와 생명 주기
- 지역 변수(매개 변수 포함) : 생존 주기 짦음
- 스택 영역에 있는 스택 프레임 안에 보관
- 메서드가 종료되면 스택 프레임이 제거되고 이때 포함된 지역 변수도 제거
- 인스턴스 변수 : 지역 변수보다 생존 주기 김.
- 힙 영역을 사용, GC(가비지 컬렌션)가 발생하기 전까지 생존
- 인스턴스 변수는 동적으로 생성되고 제거된다.
- 클래스 변수 : 가장 긴 생존 주기
- 메서드 영역의 static 영역에 보관, 메서드 영역은 프로그램 전체에서 사용하는 공용 공간
- 해당 클래스가 JVM에 로딩되는 순간 클래스 변수 생성, JVM 종료될 때까지 생존
- 정적인 이유는 프로그램 실행 시점에 만들어지고, 프로그램 종료 시점에 제거되기 때문
DataCountMain3.java (추가 - 정적 변수 접근법)
// 추가
// 인스턴스를 통한 접근
Data3 data4 = new Data3("D");
System.out.println(data4.count);
// 클래스를 통한 접근
System.out.println(Data3.count);
- 둘다 정적 변수에 접근
- 정적 변수의 경우 인스턴스를 통한 접근은 추천하지 않는다.
코드를 읽을 때 마치 인스턴스 변수에 접근하는 것 처럼 오해할 수 있기 때문 - 정적 변수는 클래스에서 공용으로 관리하기에 클래스를 통해서 접근
7-8. static 메서드1
DecoUtil1.java
package static2;
public class DecoUtil1 {
public String deco(String str) { // 문자열을 꾸미는 기능 제공
String result = "*" + str + "*";
return result;
}
}
DecoMain1.java
package static2;
public class DecoMain1 {
public static void main(String[] args) {
String s = "hello java";
DecoUtil1 utils = new DecoUtil1();
String deco = utils.deco(s);
System.out.println("before: " + s);
System.out.println("after: " + deco);
}
}
- deco() 메서드를 호출하기 위해서는 DecoUtil1 의 인스턴스를 먼저 생성해야 한다.
- deco() 라는 기능은 멤버 변수도 없고, 단순히 기능만 제공할 뿐
- 인스턴스가 필요한 이유는 멤버 변수(인스턴스 변수)등을 사용하는 목적이 큰데,
이 메서드는 사용하는 인스턴스 변수도 없고 단순히 기능만 제공
DecoUtil2.java (static 메서드)
package static2;
public class DecoUtil2 {
public static String deco(String str) { // static 추가
String result = "*" + str + "*";
return result;
}
}
DecoMain2.java
package static2;
public class DecoMain2 {
public static void main(String[] args) {
String s = "hello java";
String deco = DecoUtil2.deco(s);
System.out.println("before: " + s);
System.out.println("after: " + deco);
}
}
- static 이 붙은 정적 메서드는 정적 변수처럼 인스턴스 생성 없이 클래스 명을 통해서 바로 호출
- 정적 메서드 덕분에 불필요한 객체 생성 없이 편리하게 메서드를 사용
클래스 메서드(= 정적 메서드): 인스턴스 생성 없이 클래스를 통해 호출 가능
인스턴스 메서드: 인스턴스를 생성해야 호출 가능
7-9. static 메서드2
정적 메서드 사용법
- static 메서드는 static만 사용 가능
- 클래스 내부의 기능을 사용할 때, 정적 메서드는 static 이 붙은 정적 메서드나 정적 변수만 사용 가능
- 클래스 내부의 기능을 사용할 때, 정적 메서드는 인스턴스 변수나, 인스턴스 메서드를 사용 불가
- 반대로 모든 곳에서 static 을 호출 가능: 접근 제어자만 허락한다면 클래스를 통해 모든 곳에서 static 을 호출
DecoData.java
package static2;
public class DecoData {
private int instanceValue;
private static int staticValue;
public static void staticCall() {
//instanceValue++; // 인스턴스 변수 접근, compile error
//instanceMethod(); // 인스턴스 메서드 접근, compile error
staticValue++; // 정적 변수 접근
staticMethod(); // 정적 메서드 접근
}
public void instanceCall() {
instanceValue++; // 인스턴스 변수 접근
instanceMethod(); // 인스턴스 메서드 접근
staticValue++; // 정적 변수 접근
staticMethod(); // 정적 메서드 접근
}
private void instanceMethod() {
System.out.println("instanceValue=" + instanceValue);
}
private static void staticMethod() {
System.out.println("staticValue=" + staticValue);
}
}
- static 메서드는 static 변수, static 메서드 접근 가능 => 인스턴스 (변수, 메서드) 접근 불가
DecoDataMain.java
package static2;
public class DecoDataMain {
public static void main(String[] args) {
System.out.println("1.정적 호출");
DecoData.staticCall();
System.out.println("2.인스턴스 호출1");
DecoData data1 = new DecoData();
data1.instanceCall();
System.out.println("3.인스턴스 호출2");
DecoData data2 = new DecoData();
data2.instanceCall();
}
}
정적 메서드가 인스턴스의 기능을 사용할 수 없는 이유
- 정적 메서드는 클래스 이름을 통해 바로 호출 가능 => 참조값 없이 호출
- 특정 인스턴스의 내부 기능을 사용하려면 참조값을 알아야 함
- 정적 메서드 내부에서 참조값이 없어 인스턴스 변수나 인스턴스 메서드를 사용할 수 없다.
(외부에서) 객체의 참조값을 직접 매개변수로 전달
=> 정적 메서드도 인스턴스의 변수나 메서드를 호출 가능
public static void staticCall(DecoData data) {
data.instanceValue++;
data.instanceMethod();
}
7-10. static 메서드3
멤버 메서드의 종류 (용어 정리)
- 인스턴스 메서드 : static이 붙지 않은 멤버 메서드
- 클래스 메서드 (= 정적 메서드, static 메서드) : static이 붙은 멤버 메서드
정적 메서드 활용
- 객체 생성이 필요없이 메서드의 호출만으로 필요한 기능을 수행할 때 사용
- 간단한 메서드 하나로 끝나는 유틸리티성 메서드에 자주 사용
- 수학의 여러가지 기능을 담은 클래스를 만들 때, 인스턴스 변수 없이 입력한 값을 계산하고 반환하는 것이 대부분
이럴 때 정적 메서드를 사용해서 유틸리티성 메서드를 만들면 좋음
DecoDataMain.java (추가 - 정적 메서드 접근법)
// 추가
// 인스턴스를 통한 접근
DecoData data3 = new DecoData();
data3.staticCall();
// 클래스를 통한 접근
DecoData.staticCall();
- 둘은 차이 없이 결과적으로 정적 메서드에 접근.
- But, 인스턴스를 통한 접근은 추천하지 않음(인스턴스 메서드에 접근하는 것 처럼 보일 수 있기 때문)
static import
alt+enter => Add on-demand static import for 'static2.DecoData'
//import static static2.DecoData.staticCall;
import static static2.DecoData.*; // *: 전체 static
// 클래스명 생략 가능
DecoData.staticCall(); => staticCall()
DecoData.staticCall(); => staticCall()
DecoData.staticCall(); => staticCall()
main() 메서드는 정적 메서드
- 인스턴스 생성 없이 실행하는 가장 대표적인 메서드가 바로 main() 메서드
- main() 메서드는 프로그램을 시작하는 시작점이 되는데, main() 메서드가 static 이기 때문
- 정적 메서드인 main() 이 호출하는 메서드에는 정적 메서드를 사용
- 정확히 말하자면 정적 메서드는 같은 클래스 내부에서 정적 메서드만 호출 가능
// 예시
package oop1;
public class ValueDataMain {
public static void main(String[] args) {
ValueData valueData = new ValueData();
add(valueData);
add(valueData);
add(valueData);
System.out.println("최종 숫자=" + valueData.value);
}
static void add(ValueData valueData) {
valueData.value++;
System.out.println("숫자 증가 value=" + valueData.value);
}
}
7-11. 문제와 풀이
Car.java
package static2.ex;
public class Car {
private static int cars;
private String name;
void Car(String name) {
System.out.println("차량 구입, 이름: "+name);
this.name = name;
cars++;
}
static void showTotalCars() {
System.out.println("구매한 차량 수: " + cars);
}
}
MathArrayUtils.java
package static2.ex;
public class MathArrayUtils {
private MathArrayUtils() {
//private 인스턴스 생성을 막는다.
}
public static int sum(int[] array) {
int sum = 0;
for (int i : array) {
sum+=i;
}
return sum;
}
public static double average(int[] array) {
return sum(array)/(double)array.length;
}
public static int min(int[] array) {
int minValue = array[0];
for (int i : array) {
if (i < minValue) {
minValue = i;
}
}
return minValue;
}
public static int max(int[] array) {
int maxValue = array[0];
for (int i : array) {
if (i > maxValue) {
maxValue = i;
}
}
return maxValue;
}
}
인스턴스 생성 막기
private MathArrayUtils() {}