자바 메모리 구조

메서드 영역 : 클래스 정보를 보관.
스택 영역: 실제 프로그램이 실행되는 영역. 메서드를 실행할 때 마다 하나씩 쌓인다.
힙 영역 : 객체(인스턴스)가 생성되는 영역. new 명령어 사용.

메서드 영역
메서드 영역은 프로그램을 실행하는 공통 데이터를 관리.
-클래스 정보 : 클래스의 실행 코드, 필드, 메서드와 생성자 코드등, 모든 실행 코드가 존재
- static 영역: static 변수들이 보관.
- 런타임 상수 풀: 프로그램을 실행하는데 공통 리터럴 상수를 보관,
stack 영역
자바 실행시 하나의 실행 스택이 생성. 각 스택 프레임은 지역변수, 중간 연산결과, 호출 정보등을 포함
- stack frame : 스택 영역에 쌓이는 네모 박스가 하나의 스택 프레임.
메서드 호출할 때마다 하나의 스택 프레임이 쌓이고, 메서드가 종료되면
해당 스택 프레임 제거
Heap 영역
객체 (인스턴스)의 배열이 생성되는 영역, 가비지 컬렉션이 이뤄지는 주요 영역.
참고) 스텍영역은 각 쓰레드별로 하나의 실행 스텍이 생성. 따라서 쓰레드 수 만큼 스택 영역이 생성

자바에서 특정 클래스로 100개의 인스턴스를 생성, 힙 메모리에 100개 인스턴스 생성.
각 인스턴스는 내부에 변수와 메서드를 갖는다. 같은 클래스로 부터 생성된 객체라도, 인스턴스 내부의 변수 값은 서로 다를 수 있지만 메서드는 공통된 코드를 공유. 따라서 객체가 생성될 때, 인스턴스 변수에느 메모리가 할당. 메서드에 대한 새로운 메모리 할당은 없다. 메서드는 메서드 영역에서 공통으로 관리
Stack Queue 자료 구조

넣는 과정 빼는 과정
1 2 3 | 3 2 1
후입 선출 (=stack)
가장 마지막에 넣은 3번이 가장 먼저 나온다.
선입 선출 (=queue)
가장 먼저 넣은 것이 가장 우선 나온다.

stack 영역
public class Data {
private int val;
public Data(int val){
this.val = val;
}
public int getVal(){
return val;
}
}
public class JavaMemoryMain {
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");
}
}

자바 프로그램을 실행하면 main을 실행. 이때 main을 위한 스택 프레임이 하나 생성.
-> main 스택 프레임은 내부에 args 라는 매개변수를 갖는다.
main은 method1을 호출, stack frame 생성
-> method1은 m1, cal 지역 변수를 갖는다. 해당 지역 변수들이 stack frame에 포함
method1은 method2를 호출 -> stackframe 생성

method2 종료 -> 스텍 프레임 제거, m2도 제거. method2 스택 프레임이 제거, method1로 돌아간다. method1에 처음으로 돌아가는 것X -> method1에서 method2 호출 시점으로 돌아간다.
method1 종료, method1 stack frame 제거, 지역 변수, cal 제거.
main 종료
Stack 영역과 Heap 영역
public class Data {
private int val;
public Data(int val){
this.val = val;
}
public int getVal(){
return val;
}
}
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 data = new Data(10); //힙 영역에 생성
method2(data);
System.out.println("method2 end");
}
static void method2(Data data) {
System.out.println("method2 start");
System.out.println("data.val = " + data.getVal());
System.out.println("method2 end");
}
}
main 실행


main에서 method1 실행 -> method1 stack frame 생성
method1은 지역변수로 data1을 소유. 지역 변수도 stack frame에 포함
method1은 new Data(10)을 사용, 힙 영역에 data인스턴스를 생성, 참조값 data1을 보관

method1은 method2를 호출하면서 data2 매개변수 x001 참조값을 넘긴다.
method1 & method2의 data1 & data2는 같은 참조값을 갖는다.

method2 종료, method2 stack frame 제거되면서 매개변수 data2 제거

method1 종료 -> method1 stack frame 제거, data1 제거

method1 종료 직후 -> stack frame 제거, data1 제거
참조값 남는 곳 없다.
결과적으로 프로그램에서 더는 사용 X
GC는 이렇게 참조 모두 사라진 인스턴스를 찾아서 메모리에서 제거
static 변수
package chap16.static1;
public class Data1 {
private String name;
private int cnt;
public Data1(String name){
this.name = name;
cnt++;
}
public String getName() {
return name;
}
public int getCnt() {
return cnt;
}
}
public class DataCntMain {
public static void main(String[] args) {
Data1 data1 = new Data1("A");
System.out.println("data1.getCnt() = " + data1.getCnt());
Data1 data2 = new Data1("B");
System.out.println("data2.getCnt() = " + data2.getCnt());
Data1 data3 = new Data1("C");
System.out.println("data3.getCnt() = " + data3.getCnt());
}
}
객체를 생성할때 마다 data1 인스턴스는 새로 만들어진다.
cnt 변수도 새로 생성된다.


인스턴스에 사용되는 멤버 변수 cnt 값은 인스턴스 끼리 공유 되지 않는다.
public class Counter {
public int cnt;
}
public class Data2 {
private String name;
public Data2(String name, Counter counter){
this.name = name;
counter.cnt++;
}
}
public class DataCntMain2 {
public static void main(String[] args) {
Counter counter = new Counter();
Data2 data1 = new Data2("A", counter);
System.out.println("A counter = " + counter.cnt);
Data2 data2 = new Data2("B", counter);
System.out.println("B counter = " + counter.cnt);
Data2 data3 = new Data2("C", counter);
System.out.println("C counter = " + counter.cnt);
}
}
cnt 인스턴스를 공용으로 만든 덕에 객체를 생성할 때마다 정확히 값을 증가

data2("A") 인스턴스를 생성하면 생성자를 통해 cnt 인스턴스에 있는 cnt 값 하나 증가

data2("B") 인스턴스를 생성하면 생성자를 통해 cnt 인스턴스에 있는 cnt 값 하나 증가

data2("C") 인스턴스를 생성하면 cnt 인스턴스에 있는 cnt 값을 하나 증가

결과적으로 data2 인스턴스가 3개 됨.
Static 변수 2
public class Data3 {
public String name;
public static int cnt;
public Data3(String name){
this.name = name;
cnt++;
}
}
static 키워드를 사용하면 공요으로 함께 사용하는 변수를 생성 할 수 있다.
멤버 변수에 static을 붙이게 되면 static 변수, 정적 변수, 클레스 변수
public class DataCntMain3 {
public static void main(String[] args) {
Data3 d1 = new Data3("A");
System.out.println("A cnt = " + Data3.cnt);
Data3 d2 = new Data3("B");
System.out.println("B cnt = " + Data3.cnt);
Data3 d3 = new Data3("C");
System.out.println("C cnt = " + Data3.cnt);
//인스턴스를 통한 접근
Data3 d4 = new Data3("D");
System.out.println(d4.cnt); //권장하는 방법 X -> 가져다 쓰는 입장에서 햇갈림
System.out.println(Data3.cnt);
}
}

static이 붙은 멤버 변수는 메서드 영역에서 관리
-> static이 붙은 멤버 변수 cnt는 인스턴스 영역에 생성되지 않는다. 대신 메서드 영역에서 이 변수를 관리
data3("A") 인스턴스를 생성하면 생성자 호출
생성자에는 cnt++ 코드가 있다. 따라서 cnt static 붙은 정적 변수

data3("B") 인스턴스를 생성하면 생성자 호출
cnt++ 코드 -> 변수값 하나 증가

위와 동일

최종적으로 메서드 영역에 있는 cnt 값 = 3
static이 붙은 정젹 변수라면 Data3.cnt와 같이 클래스명 + .(dot) + 변수명으로 접근
Static 변수 3
용어 정리
public class Data3 {
public String name;
public static int count; //static
}
멤버 변수의 종류
인스턴스 변수
변수와 생명 주기
-인스턴스 변수
인스턴스에 있는 멤버변수를 인스턴스 변수라고 한다. 인스턴스 변수는 힘 영역을 사용
힙 영역은 GC가 발생 하기 전까지 생존하기 때문에 보통 지역 변수보다 생존 주기 길다
-클래스 변수
static 영역에 보관, 메서드 영역은 프로그램 전체에서 사용하는 공용공간. 클래스 변수는 해당 클래스가 JVM에 로딩 되는 순간 생성
가장 긴 생명 주기
정적 변수 접근 법
static 변수는 클래스르 통해 바로 접근 가능, 인스턴스를 통해서 접근 가능
인스턴스를 통한 접근
data.cnt / 추천하지는 않는다. 왜냐면 코드 읽는 입자엥서는 변수에 접근 처럼 오해 가능성
클래스를 통한 접근
Data3.cnt
Static 메서드 1
인스턴스 메서드
public class DecoUtil1 {
public String deco(String str) {
String rst = "* " + str + " *";
return rst;
}
}
public class DecoMain1 {
public static void main(String[] args) {
String s = "Hello java";
DecoUtil1 util1 = new DecoUtil1();
String deco = util1.deco(s);
System.out.println("before " + s);
System.out.println("after " + deco);
}
}
public class Decoutil2 {
public static String deco(String str) {
String rst = "* " + str + " *";
return rst;
}
}
static 메서드
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을 붙일 수 있다. 이를 정적 메서드 또는 클레스 메서드
static 메서드 2
정적 메서드는 객체 생성 X -> 클래스에 있는 메서드를 바로 호출
정적 메서드는 언제나 사용 할 수 있는 것은 X
정적 메서드 사용법
static 메서드는 static만 사용 가능
-> 클래스 내부의 기능을 사용할 때 정적 메서드는 static이 붙은 정적 메서드나 정적 변수만 사용
-> 클래스 내부의 기능을 사용시, 정적 메서드는 인스턴스 변수나, 인스턴스 메서드를 사용X
반대로 모든 곳에서 static을 호출
-> 정적 메서드는 공용 기능, 따라서 접근 제어자이지만, 허락 한다면 클래스를 통해서 모든 곳 에서 staic 호출
public class DecoData {
private int instanceVal;
private static int staticVal;
public static void staticCall() {
// instanceVal++; compile Error, 인스턴스 변수 접근
// instanceMethod(); compile Error, 인스턴스 메서드 접근
staticVal++;//정적 변수 접근
staticMethod();
}
public void instanceCall(){
instanceVal++;
instanceMethod();
staticVal++;
staticMethod();
}
public void instanceMethod() {
System.out.println("instanceVal = " + instanceVal);
instanceVal++;
}
public static void staticCall(DecoData data){
data.instanceVal++;
data.instanceMethod();
}
public static void staticMethod() {
System.out.println("staticVal = " + staticVal);
}
}
이번 예제에서는 접근 제어자를 적극 활용해서 필드를 포함한 외부에서 직접 필요하지 않은 기능은 모두 막아두었다.
staticCall 메서드
-> 이 메서드는 정적 메서드, 따라서 static만 사용 가능. 정적 변수, 정적 메서드에는 접근 가 능, 인스턴스 변수나 인스터느 메서드에 접근하면 compileError
instanceCall
-> 인스턴스 메서드. 모든 곳에서 공용인 static 호출 가능,
public class DecoDataMain {
public static void main(String[] args) {
System.out.println("1. 정적 호출");
staticCall();
System.out.println("2. 인스턴스 호출");
DecoData data = new DecoData();
data.instanceCall();
System.out.println("3. 인스턴스 호출");
DecoData data1 = new DecoData();
data1.instanceCall();
DecoData data2 = new DecoData();
staticCall();
staticCall(); // 추천 하는 방향은 아니다.
}
}

정적 메서드가 인스턴스의 기능을 사용 할 수 X
-> 정적 메서드는 클래스의 이름을 통해 바로 호출 가능. 인스턴스처럼 참조값의 개념이 없음
특정 인스턴스의 기능을 사용하려면 참조값을 알아야 하는데, 정적 메서드는 참조 값 없이 호출
따라서 정적 메서드 내부에서난 인스턴스 변수나 인스턴스 메서드를 사용 X
static 메서드 3
용어 정리
인스턴스 메서드 : static이 붙지 않은 메서드
클레스 메서드 : 클레스 메서드, 정적 메서드
static이 붙지 않은 멤버 메서드는 인스턴스를 생성해야 사용 가능, 인스턴스에 소속.
따라서 인스턴스 메서드라고 한다. static이 붙은 멤버 메서드는 인스턴스와 무관하게 클래스에 바로 접근해서 사용 가능
정적 메서드 활용
정적메서드는 객체 생성 필요 없이 메서드의 호출만으로 필요 기능을 수행
static import
정적 메서드를 사용 할때 해당 메서드를 다음과 같이 자주 호출 해야 한다면 static import 사용
DecoData.staticCall();
DecoData.staticCall();
DecoData.staticCall();
main 메서드는 정적 메서드
인스턴스 없이 실행하는 가장 대표적 메서드가 main
main 메서드는 프로그램 시작하는 시점 실행. 객체 X 실행 가능
정적 메서드는 정적 메서드만 호출. 따라서 정적 메서드인 main이 호출하는 메서드에는 정적 사용
문제풀이
1.
public class Car {
public String model;
public static int totalCar;
public Car(String model){
this.model = model;
totalCar++;
}
public void showCar(Car car){
System.out.println("차량 구입, 이름 : " + car.model);
}
public static void showTotalCar(){
System.out.println("구매한 차량 수 : " + totalCar);
}
}
public class CarMain {
public static void main(String[] args) {
Car car1 = new Car("K3");
Car car2 = new Car("G80");
Car car3 = new Car("Model Y");
car1.showCar(car1);
car2.showCar(car2);
car3.showCar(car3);
Car.showTotalCar();
}
}
public class MathUtils {
public static int sum(int[] val) {
int rst = 0;
for (int i = 0; i < val.length; i++) {
rst += val[i];
}
return rst;
}
public static double average(int[] val) {
double rst = 0;
for (int i = 0; i < val.length; i++) {
rst += val[i] / val.length;
}
return rst;
}
public static int min(int[] val) {
int min = val[0];
for (int i = 0; i < val.length; i++) {
if (val[i] < min) {
min = val[i];
}
}
return min;
}
public static int max(int[] val) {
int max = val[0];
for (int i = 0; i < val.length; i++) {
if (val[i] > max) {
max = val[i];
}
}
return max;
}
}
public class MathUtilsMain {
public static void main(String[] args) {
int[] val = {1, 2, 3, 4, 5};
System.out.println("sum = " + MathUtils.sum(val));
System.out.println("average = " + MathUtils.average(val));
System.out.println("min = " + MathUtils.min(val));
System.out.println("max = " + MathUtils.max(val));
}
}