JVM 이란 Java Virual Machine의 약자이며, 가상 자바 머신이라고 불린다. Java의 바이트 코드를 해석하고 실행하는 역할을 한다.
JVM 실행 순서
1. java 파일을 컴파일러를 통해 .class 파일로 변환한다.
2. class 파일을 JVM의 ClassLoader에게 보낸다.
3. 클래스로더에서 JVM 런타임 영역으로 로딩하여 메모리에 올린다.
jvm이 메모리 구조를 내부에서 가지고 동작.

크게 3가지

GC(가비지 컬렉션) : heap 영역에서 동적으로 할당했던 메모리 중 필요 없게 된 메모리 객체를 모아 주기적으로 제거하는 프로세스
참고 : 스택 영역은 각 쓰레드 별로 하나의 실행 스택이 생성됨. 따라서 쓰레드 수 만큼 스택 영역이 생성됨. 지금은 쓰레드를 1개만 사용하므로 스택 영역도 하나다. 쓰레드에 대한 부분은 멀티 쓰레드를 학습해야 이해 가능


하지만 프로그램 실행과 메서드 호출에는 스택 구조가 적합.
스택 영역
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");
}
}
실행 결과
main start
method1 start
method2 start
method2 end
method1 end
main end

시작
1. 자바 프로그램을 처음 실행하면 main()을 실행. 이때 main()을 위한 스택 프레임이 하나 생성된다.
종료
1. method()가 종료된다. 스택 프레임이 제거되고, 매개변수 m2도 제거된다. method2 스택 프레임이 제거 되었으므로 프로그램은 method1()로 돌아간다.
2. method1()이 종료된다. method() 스택 프레임이 제거되고, 지역 변수 m1, cal도 제거된다. 프로그램은 main()으로 돌아간다.
정리
- 자바는 스택 영역을 사용해서 메서드 호출과 지역 변수를 관리한다.
- 메서드를 계속 호출하면 스택 프레임이 쌓인다.
- 지역 변수는 스택 영역에서 관리한다.
- 스택 프레임이 종료되면 지역 변수도 함께 제거된다.
- 스택 프레임이 모두 제거되면 프로그램도 종료된다.
Data
package memory;
public class Data {
private int value;
public Data(int value) {
this.value = value;
}
public int getValue() {
return value;
}
}
JavaMemoryMain2
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()에서 Data 클래스의 인스턴스를 생성






참고: 힙 영역 외부가 아닌, 힙 영역 안에서만 인스턴스끼리 서로 참조하는 경우애도 GC의 대상이 된다.
정의: 메모리에 한번 할당되어 프로그램이 종료될 때 해제되는 것
static 키워드는 주로 멤버 변수와 메서드에 사용된다.
왜 필요한지?
특정 클래스를 통해서 생성된 객체의 수를 세는 단순한 프로그램이다.
Data1
package static1;
public class Data1 {
public String name;
public int count;
public Data1(String name) {
this.name = name;
count++;
}
}
DataCountMain1
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);
}
}




정리: 인스턴스를 생성하면 이전 인스턴스는 관계없는 새로운 인스턴스를 생성한다. 인스턴스에 사용되는 멤버 변수 count 값은 인스턴스끼리 서로 공유되지 않는다.
Data2
package static1;
public class Data2 {
public String name;
public Data2(String name, Counter counter) {
this.name = name;
counter.count++;
}
}
DataCountMain2
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("B", counter);
System.out.println("B count=" + counter.count);
Data2 data3 = new Data2("C", counter);
System.out.println("C count=" + counter.count);
}
}




Data1
package static1;
public class Data3 {
public String name;
public static int count; //static
public Data3(String name) {
this.name = name;
count++;
}
}
DataCountMain3
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);
}
}
정적 변수에 접근하는 방법 = 클래스명에 .(dot)을 사용한다. 마치 클래스에 직접 접근하는 것처럼 느껴짐.


정리: static 변수는 클래스인 붕어빵 틀이 특별히 관리하는 변수이다. 붕어빵 틀은 1개이므로 클래스 변수도 하나만 존재한다. 반면에 인스턴스 변수는 붕어빵인 인스턴스의 수 만큼 존재한다.
public class Data3 {
public String name;
public static int count; //static
}
예제 코드에서 name , count 는 둘다 멤버 변수이다.
멤버 변수(필드)는 static 이 붙은 것과 아닌 것에 따라 다음과 같이 분류할 수 있다.
인스턴스 변수 : static이 붙지 않은 멤버 변수 예) name
클래스 변수 : static이 붙은 변수 예) count
인스턴스 변수는 동적으로 생성되고 제거된다. 하지만 정적변수는 프로그램 실행 시점에 딱 만들어지고, 프로그램 종료 시점에 제거된다.

특정 문자열을 꾸며주는 간단한 기능을 만들어보자.
예를 들어서 "hello" 라는 문자열 앞 뒤에 을 붙여서 "hello*" 와 같이 꾸며주는 기능이다
package static2;
public class DecoUtil1 {
public String deco(String str) {
String result = "*" + str + "*";
return result;
}
}
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()라는 기능은 멤버 변수도 없고, 단순히 기능만 제공.
- 인스턴스가 필요한 이유는 멤버 변수등을 사용하는 목적이 큰데, 이 메서드는 사용하는 인스턴스 변수도 없고 단순히 기능만 제공
package static2;
public class DecoUtil2 {
public static String deco(String str) {
String result = "*" + str + "*";
return result;
}
}
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);
}
}


정리
DecoData
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);
}
}

DecoDataMain
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();
}
}

정적 메서드는 클래스의 이름을 통해 바로 호출할 수 있다. 그래서 인스턴스처럼 참조값의 개념이 없다. 특정 인스턴스의 기능을 사용하려면 참조값을 알아야 하는데, 정적 메서드는 참조값 없이 호출한다. 따라서 정적 메서드 내부에서 사용 불가능

멤버 메서드의 종류(멤버 메서드: 클래스에 정의한 메서드)
static이 붙지 않은 멤버 메서드 = 인스턴스를 생성해야 생성 가능하고, 인스턴스에 소속
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);
}


답:
package static2.ex;
public class Car {
private static int totalCars;// 공용
private String name;//인스턴스마다 다르므로 static이 안붙음
public Car(String name) {
System.out.println("차량 구입, 이름: " + name);
this.name = name;
totalCars++;
}
public static void showTotalCars() {
System.out.println("구매한 차량 수= " + totalCars);
}
}
이 문제는
이게 중요하다.
2번 문제


정답
package static2.ex;
import static static2.ex.MathArrayUtils.*;
public class MathArrayUtilsMain {
public static void main(String[] args) {
int[] values = {1, 2, 3, 4, 5};
System.out.println("sum=" + sum(values));
System.out.println("average=" + average(values));
System.out.println("min=" + min(values));
System.out.println("max=" + max(values));
}
}
package static2.ex;
public class MathArrayUtils {
private MathArrayUtils(){
//private 인스턴스 생성을 막는다.
}
public static int sum(int[] values) {
int total = 0;
for (int value : values) {
total += value;
}
return total;
}
public static double average(int[] values) {
return (double) sum(values) / values.length;
}
public static int min(int[] values) {
int minValue = values[0];
for (int value : values) {
if (value < minValue) {
minValue = value;
}
}
return minValue;
}
public static int max(int[] values) {
int maxValue = values[0];
for (int value : values) {
if (value >maxValue) {
maxValue = value;
}
}
return maxValue;
}
}
정리