JVM 메모리 영역

  • JVM(자바가상머신) 메모리 영역
    Method | Stack | Heap

  • JVM은 OS로부터 메모리를 할당받으면 해당 메모리를 3개의 영역으로 나누어서 관리한다

    1) Method:
    클래스 바이트 코드가 로드되는 영역
    로드 시점은 실행할때 (java.exe)이다.

    2) Stack:
    메서드 내에 선언된 지역변수,
    메서드의 매개변수,
    리턴값 등 저장

    3) Heap : 객체의 인스턴스가 올라오는 영역

     class Cat{
    	 String name="tomcat";
    	 int age=5;
    	 
    	 public int getAge(){
    		 return age;
    	 }
        
         public static void main(String[] args){
    		 int x=7;
    		 Cat c=new Cat();
    		 System.out.println(c.getAge()); 
            // JVM에 의해 메서드 호출 시 자동으로 this 매개변수 전달, this=c 대입
    	 }
    }
    MethodStackHeap
    class CatgetAge() frameCat 인스턴스 @ (c)
    - getAge()└── this = c┌ name = "tomcat"
    - main()main() frame│ age = 5
    ├── c = @└──────────────────────────
    ├── x = 7
    └── args = null

    ✅ this = c
    JVM은 메서드 호출 시 자동으로 this라는 숨겨진 매개변수를 전달
    예: c.getAge() 호출 시 JVM은 내부적으로
    → getAge(c)처럼 this = c 를 메서드에 넘겨줌
    이 this를 통해 실제 객체를 알 수 있으므로, 그에 맞는 메서드를 동적으로 바인딩할 수 있게 됨
    즉, this = c 는 메서드가 "누구에 의해 호출되었는지" 알려주는 역할

JVM 메모리 영역 확인문제

  1. 클래스가 객체를 설계한 것이라면, 실행 타임(=Runtime)시 메모리에 올라가는 객체 한 단위를 (인스턴스)라고 한다.

  2. Car a=new Car() 에서 a 변수에는 객체의 주소값이 담겨 있다. 이렇게 객체의 주소값을 가지는 변수를 (레퍼런스) 변수라고 한다.

  3. 객체 생성 후 프로그래머가 따로 메모리 관리를 해주지 않아도 자동으로 사용되지 않는 인스턴스들을 소멸시키는 JVM의 메모리 청소 기능을 GC (garbage collection)이라 한다.

  4. 메인 메서드는 개발자가 직접 호출 할 수 없으며 명령 프롬프트에서 (java.exe) 파일 실행 시 자동 호출된다.

  5. 레퍼런스 변수 값을 출력해보면 @ 형태의 데이터가 출력되는데, 이것은 객체 자체의 값이 아니라 그 객체의 (주소) 값이다.

  6. 클래스로부터 생성되는 인스턴스가 메모리에 올라갈 때 클래스에 작성된 (멤버)변수 중 인스턴스 변수는 각각의 인스턴스 마다 할당되어진다.

  7. JVM 도 프로그램 이므로 (OS)로 부터 메모리를 할당 받아 사용한다. 이때 JVM 에서는 메모리 영역을 크게 (메서드 (static)), (스택), (힙) 영역으로 분리하여 운영한다.

  8. 객체 생성 시 프로그래머의 의도와는 상관없이 new 연산자 뒤에서 무조건 호출되는 메서드를 (생성자 메서드)라고 하는데, 반드시 클래스명과 명칭이 동일해야 하며, 절대로 반환형을 두어서는 안된다

  9. 현실의 사물을 객체(=오브젝트)을 Java 언어로 모델링 한 설계도를 (클래스)라 하며, 이로 부터 메모리에 생성된 객체 한 단위를 (인스턴스)라 한다.

  10. 같은 클래스 내에 작성된 변수를 (멤버) 변수, 메서드를 (멤버) 메서드라 한다.

  11. 클래스 코드는 JVM 메모리 영역 중 (메서드) 영역에 로드 되는데 , 로드 시점은 클래스를 (실행할 때)이다.

  12. 모든 메서드 내의 매개변수를 포함한 지역변수는 메서드 호출 시 JVM의 메모리 영역 중 (Stack) 영역에 생성 및 관리된다.

  13. Car a = new Car() 에 의해 생성된 Car 객체의 인스턴스는 JVM의 메모리 영역 중 (heap) 영역에 생성 및 관리된다.

<참고>
메모리에 올라간다=실행한다
컴파일≠메모리에 올라간다
LIFO : Last In, First Out (후입선출: 스택구조를 설명)


인스턴스 변수와 클래스 변수

인스턴스 변수

  • 정의 방법: static 키워드를 사용하지 않고 선언

  • 메모리 위치: 힙 메모리

  • 생성 시점: new 연산자로 객체를 생성할 때마다
    (인스턴스가 Heap 메모리에 올라가야 사용가능)

  • 공유 범위: 객체마다 개별적으로 존재 (→ 독립성)

  • 접근 방법: 반드시 객체를 통해 접근 (객체명.변수명)

클래스 변수 (static 변수)

  • 정의 방법: static 키워드를 사용해서 선언

  • 메모리 위치: 메서드 영역

  • 생성 시점: 클래스가 처음 로드될 때 단 1번만 생성됨
    (인스턴스 생성 없이도 사용가능)

  • 공유 범위: 모든 인스턴스가 공유
    (더 정확히 말하자면, 클래스 변수값은 클래스 전체에서 공유)

  • 접근 방법: 클래스명.변수명 또는 객체명.변수명

<예제 1> 인스턴스 변수의 기본 초기화 + 클래스 변수(static 변수)의 "접근 방식"

class Member {
	int price; //(클래스가 보유한 멤버변수만) 초기값 할당하지 않아도 컴파일러에 의해 초기화 (기본값 0)
	int age=23; //인스턴스 변수
	static int money=23; //클래스 변수 (클래스에 붙어있음)
	
	public void talk(){
	}
}

 public class UseMember{
	 static String k="안녕"; 
	 public static void main(String[] args){
		//방법1 : 클래스명으로 접근
		UseMember.k="헬로";
		
		//방법2 : 같은 클래스로 직접 접근
        //메인과 클래스 변수가 같은 클래스 안에 있을 경우(둘 다 static이면서)
        //앞에 클래스명 안붙여도 됨 → 클래스 변수값은 클래스 전체에서 공유되니까
		k="하이";
	}
}

static 변수는 new로 인스턴스를 생성해도 인스턴스에 딸려오지 않고, 클래스와 함께 존재 (딱 붙어있음)

.java 파일에는 public 클래스는 하나만 가능하고, 그 클래스 이름과 파일 이름이 반드시 같아야 함.

<예제 2> 클래스 변수(static 변수)의 "공유성"

→ 클래스 변수는 클래스에 속하며, 해당 클래스로 생성된 모든 인스턴스가 하나의 값을 공유한다는 특성

public class Tree {
	int root=1; 
	static int height=25; 
	
	public static void main(String[] args) {
		Tree t1 = new Tree(); 
		Tree t2 = new Tree();
		
		t1.height=30;
		
		System.out.println(t2.height);
		System.out.println(Tree.height);
	}
}
MethodStackHeap
class Treemain() frameTree 인스턴스 @ (t1)
- height (static) = 25 → 30├── t2 = @┌ root = 1
├── t1 = @└─────────────────────────────────────
main()└── args = nullTree 인스턴스 @ (t2)
┌ root = 1
└─────────────────────────────────────
⚠ height는 두 객체가 아닌
클래스(Method) 영역에 1개만
존재. 현재 값 = 30

Tree t1 = new Tree();
→ Heap에 Tree 인스턴스 생성 (@)
Tree t2 = new Tree();
→ 또 다른 인스턴스 생성 (@)
t1.height = 30;
height는 static 변수이므로 클래스(Tree) 단 하나만 존재
즉, t1.height, t2.height, Tree.height는 모두 같은 변수를 가리킴
그래서 Tree.height 값도 30이 됨
System.out.println(t2.height); → 30 출력
System.out.println(Tree.height); → 30 출력

<예제3> 인스턴스 변수의 "독립성" 과 "캡슐화(encapsulation)"

→ 인스턴스 변수는 객체마다 개별적으로 존재한다 → "독립성"
→ 메서드를 통해 변수에 접근하고 수정한다 → "캡슐화(encapsulation)"

class  Book{
	String title="해리포터";
	int page=300;

	public int getPage(){
		return page;
	}
	public void setPage(int p){
		page=p;
	}
}


class UseBook{
	public static void main(String[] args){

		Book b=new Book();
		b.setPage(150);
		int page=b.getPage();
		System.out.println("페이지 수는 "+page);
	}
}
MethodStackHeap
class BookgetPage() frameBook 인스턴스 @ (b)
- getPage()└── this = b┌ title = "해리포터"
- setPage()setPage() frame│ page = 300 →150
├── p = 150└────────────────────────────
└── this = b
main()main() frame
├── page = 150
├── b = @
└── args = null

Book b = new Book();
→ Heap 영역에 Book 객체 생성
→ title="해리포터", page=300 초기화됨
b.setPage(150);
→ Stack에 setPage() 프레임 생성
→ this = b, p = 150
→ Heap의 page 값이 150으로 변경
b.getPage();
→ Stack에 getPage() 프레임 생성
→ this = b
→ page 값 150 반환
System.out.println("페이지 수는 " + page);
→ 콘솔에 "페이지 수는 150" 출력됨


Java 변수 초기화 및 범위 개념

구분존재 위치초기화 여부특징 및 비고
전역 변수❌ 없음-Java는 전역 변수 개념 없음
멤버 변수클래스 내부 (필드)✅ 자동으로 초기화됨객체 생성 시 Heap에 저장됨
└ 인스턴스 변수(static X)✅ 기본값으로 초기화객체마다 개별로 존재
└ 클래스 변수(static O)✅ 기본값으로 초기화클래스당 1개, Method 영역에 저장
지역 변수메서드 내부❌ 직접 초기화해야 함Stack에 저장됨, 초기화하지 않으면 오류

(Java에서 메서드(method)는 이름 뒤에 ( )가 붙은 코드 블록)


인스턴스 초기화 블럭과 static 초기화 블럭

인스턴스 초기화 블럭

{ }

  • 해당 클래스로부터 인스턴스를 생성할 때마다 실행되는 영역을 말함
    (= new 클래스명() 으로 객체 생성할 때마다 실행)

  • 개발 시 거의 보기 힘들다. 거의 다 생성자로 초기화하기 때문
    (생성자가 이미 객체의 변수들을 초기화 하는 역할을 함)

생성자

  • 자바의 생성자는 클래스명과 완전 동일해야함 → 클래스명( ){...}
  • 직접 생성자를 정의하지 않으면, 자바 컴파일러는 매개변수가 없고,
    아무 동작도 하지 않는 기본 생성자를 자동으로 추가

클래스,생성자,객체 명칭비교

class Car {
    Car() {
        System.out.println("생성자 호출됨");
    }
}
public class Main {
    public static void main(String[] args) {
        Car car = new Car();
    }
}
부분의미
Car클래스 이름 (사용자 정의 자료형)
Car()생성자 (클래스 이름과 같음)
car참조 변수 이름 (우리가 지은 변수 이름)
new Car()객체 생성 + 생성자 호출

static 초기화 블럭

static { }

  • 실행 직전에 실행부 실행보다 먼저 초기화 블럭을 수행
class Test {
    static {
        System.out.println("static 블럭 실행");
    }
}
public class Main {
    public static void main(String[] args) {
    	// 여기서 static 블럭 실행됨
        Test t = new Test();  
        System.out.println("main 메서드 실행");
    }
}
  1. Test 클래스가 처음 사용되므로 JVM이 클래스를 로드함
  2. 클래스 로딩 시 static 블럭 실행됨 → "static 블럭 실행" 출력
  3. 그 다음에 new Test( )로 t 객체가 생성됨 (생성자 있으면 여기서 호출)
  4. "main 메서드 실행" 출력됨

Static의 역할

Java에서 "클래스에 소속됨"을 의미

클래스 내에 static +
변수 : 모든 객체가 공유 (공통 데이터)
메서드 : 객체 없이 클래스명으로 호출 가능
블럭 : 클래스 실행 시(메모리에 저장될 때) 한 번만 실행 (초기화 목적)

역할설명예시
1. 공통 데이터모든 객체가 공유하는 값static int count;
2. 클래스명으로 직접 접근객체 없이 사용 가능Math.random()
3. 초기화 제어클래스 로딩 시 딱 1번 실행static { ... } 블럭

더 알아보기) String 클래스

  • String은 내부적으로 문자(char)들의 배열로 구현
String str = "Hello";
char ['H', 'e', 'l', 'l', 'o'] // 내부적으로는 `char[]` 배열 사용
  • String의 불변성
String str1 = "Hello";
str1 = str1 + " World";  // 새로운 String 객체가 생성됨

문자열을 변경하려고 해도 기존의 String 객체는 변하지 않고, 새로운 String 객체가 생성됨

  • String 리터럴

String은 주로 문자열(String) 리터럴을 통해 객체로 생성
String 리터럴 → 상수 풀 저장 여부 확인 → 이미 있으면 재사용
→ 없으면 새로 추가 → 메모리 절약

  • 리터럴 방식: 이미 존재하는 문자열 리터럴을 참조하려면,
    새로운 객체를 만들지 않고 기존 객체 사용
    String s = "Java";
  • new 방식: 매번 새로운 String 객체 생성
    String s = new String("Java")

리터럴 방식이 String Pool에 객체를 재사용하게 해주는 이유는 바로 String이 불변(immutable) 하기 때문

만약 String이 변경 가능한(mutable) 객체였다면,
여러 리터럴이 같은 객체를 참조할 때,
한 곳에서 변경하면 다른 곳도 원치 않게 영향을 받게 됨

리터럴 방식은 문자열을 쉽게 만들고 "공유"하기 위한 방법
불변성은 그 공유가 "안전하게 작동하도록 보장"해주는 원리


느낀 점

static...너가 뭐길래!
그래도 이제 Java 관련 개념들이 뚱딴지 같은 소리처럼 느껴지지는 않고
'아~ 이래서 이랬구나~', '이러면 이렇게 되겠네?' 어렴풋이 느낌이 오긴 온다. 배운 시간보다 회고하면서 쓰는 시간이 정~말 오래 걸리긴 하지만 (이것 저것 궁금한 것들은 꼭 찾아봐야함) 꼭꼭 씹어먹어야 체하지 않듯이, 배운 것들을 꼭꼭 잘근잘근 씹어서 온전한 내 것으로 만들어야지

profile
아이들의 가능성을 믿었던 마음 그대로, 이제는 나의 가능성을 믿고 나아가는 중입니다.🌱

0개의 댓글