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 대입
}
}
Method | Stack | Heap |
---|---|---|
class Cat | getAge() frame | Cat 인스턴스 @ (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
는 메서드가 "누구에 의해 호출되었는지" 알려주는 역할
- 클래스가 객체를 설계한 것이라면, 실행 타임(=Runtime)시 메모리에 올라가는 객체 한 단위를 (인스턴스)라고 한다.
Car a=new Car()
에서a
변수에는 객체의 주소값이 담겨 있다. 이렇게 객체의 주소값을 가지는 변수를 (레퍼런스) 변수라고 한다.
- 객체 생성 후 프로그래머가 따로 메모리 관리를 해주지 않아도 자동으로 사용되지 않는 인스턴스들을 소멸시키는 JVM의 메모리 청소 기능을 GC (garbage collection)이라 한다.
- 메인 메서드는 개발자가 직접 호출 할 수 없으며 명령 프롬프트에서 (java.exe) 파일 실행 시 자동 호출된다.
- 레퍼런스 변수 값을 출력해보면
@
형태의 데이터가 출력되는데, 이것은 객체 자체의 값이 아니라 그 객체의 (주소) 값이다.
- 클래스로부터 생성되는 인스턴스가 메모리에 올라갈 때 클래스에 작성된 (멤버)변수 중 인스턴스 변수는 각각의 인스턴스 마다 할당되어진다.
- JVM 도 프로그램 이므로 (OS)로 부터 메모리를 할당 받아 사용한다. 이때 JVM 에서는 메모리 영역을 크게 (메서드 (static)), (스택), (힙) 영역으로 분리하여 운영한다.
- 객체 생성 시 프로그래머의 의도와는 상관없이
new
연산자 뒤에서 무조건 호출되는 메서드를 (생성자 메서드)라고 하는데, 반드시 클래스명과 명칭이 동일해야 하며, 절대로 반환형을 두어서는 안된다
- 현실의 사물을 객체(=오브젝트)을 Java 언어로 모델링 한 설계도를 (클래스)라 하며, 이로 부터 메모리에 생성된 객체 한 단위를 (인스턴스)라 한다.
- 같은 클래스 내에 작성된 변수를 (멤버) 변수, 메서드를 (멤버) 메서드라 한다.
- 클래스 코드는 JVM 메모리 영역 중 (메서드) 영역에 로드 되는데 , 로드 시점은 클래스를 (실행할 때)이다.
- 모든 메서드 내의 매개변수를 포함한 지역변수는 메서드 호출 시 JVM의 메모리 영역 중 (Stack) 영역에 생성 및 관리된다.
Car a = new Car()
에 의해 생성된 Car 객체의 인스턴스는 JVM의 메모리 영역 중 (heap) 영역에 생성 및 관리된다.
<참고>
메모리에 올라간다=실행한다
컴파일≠메모리에 올라간다
LIFO : Last In, First Out (후입선출: 스택구조를 설명)
정의 방법: static 키워드를 사용하지 않고 선언
메모리 위치: 힙 메모리
생성 시점: new
연산자로 객체를 생성할 때마다
(인스턴스가 Heap 메모리에 올라가야 사용가능)
공유 범위: 객체마다 개별적으로 존재 (→ 독립성)
접근 방법: 반드시 객체를 통해 접근 (객체명.변수명
)
정의 방법: static
키워드를 사용해서 선언
메모리 위치: 메서드 영역
생성 시점: 클래스가 처음 로드될 때 단 1번만 생성됨
(인스턴스 생성 없이도 사용가능)
공유 범위: 모든 인스턴스가 공유
(더 정확히 말하자면, 클래스 변수값은 클래스 전체에서 공유)
접근 방법: 클래스명.변수명
또는 객체명.변수명
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 클래스는 하나만 가능하고, 그 클래스 이름과 파일 이름이 반드시 같아야 함.
→ 클래스 변수는 클래스에 속하며, 해당 클래스로 생성된 모든 인스턴스가 하나의 값을 공유한다는 특성
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);
}
}
Method | Stack | Heap |
---|---|---|
class Tree | main() frame | Tree 인스턴스 @ (t1) |
- height (static) = 25 → 30 | ├── t2 = @ | ┌ root = 1 |
├── t1 = @ | └───────────────────────────────────── | |
main() | └── args = null | Tree 인스턴스 @ (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 출력
→ 인스턴스 변수는 객체마다 개별적으로 존재한다 → "독립성"
→ 메서드를 통해 변수에 접근하고 수정한다 → "캡슐화(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);
}
}
Method | Stack | Heap |
---|---|---|
class Book | getPage() frame | Book 인스턴스 @ (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는 전역 변수 개념 없음 |
멤버 변수 | 클래스 내부 (필드) | ✅ 자동으로 초기화됨 | 객체 생성 시 Heap에 저장됨 |
└ 인스턴스 변수 | (static X) | ✅ 기본값으로 초기화 | 객체마다 개별로 존재 |
└ 클래스 변수 | (static O) | ✅ 기본값으로 초기화 | 클래스당 1개, Method 영역에 저장 |
지역 변수 | 메서드 내부 | ❌ 직접 초기화해야 함 | Stack에 저장됨, 초기화하지 않으면 오류 |
(Java에서 메서드(method)는 이름 뒤에 ( )가 붙은 코드 블록)
{ }
해당 클래스로부터 인스턴스를 생성할 때마다 실행되는 영역을 말함
(= 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 { }
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 메서드 실행"); } }
- Test 클래스가 처음 사용되므로 JVM이 클래스를 로드함
- 클래스 로딩 시
static 블럭
실행됨 → "static 블럭 실행" 출력- 그 다음에
new Test( )
로 t 객체가 생성됨 (생성자 있으면 여기서 호출)- "main 메서드 실행" 출력됨
클래스 내에 static
+
변수
: 모든 객체가 공유 (공통 데이터)
메서드
: 객체 없이 클래스명으로 호출 가능
블럭
: 클래스 실행 시(메모리에 저장될 때) 한 번만 실행 (초기화 목적)
역할 | 설명 | 예시 |
---|---|---|
1. 공통 데이터 | 모든 객체가 공유하는 값 | static int count; |
2. 클래스명으로 직접 접근 | 객체 없이 사용 가능 | Math.random() |
3. 초기화 제어 | 클래스 로딩 시 딱 1번 실행 | static { ... } 블럭 |
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이 변경 가능한(mutable) 객체였다면,
여러 리터럴이 같은 객체를 참조할 때,
한 곳에서 변경하면 다른 곳도 원치 않게 영향을 받게 됨
리터럴 방식은 문자열을 쉽게 만들고 "공유"하기 위한 방법
불변성은 그 공유가 "안전하게 작동하도록 보장"해주는 원리
static
...너가 뭐길래!
그래도 이제 Java 관련 개념들이 뚱딴지 같은 소리처럼 느껴지지는 않고
'아~ 이래서 이랬구나~', '이러면 이렇게 되겠네?' 어렴풋이 느낌이 오긴 온다. 배운 시간보다 회고하면서 쓰는 시간이 정~말 오래 걸리긴 하지만 (이것 저것 궁금한 것들은 꼭 찾아봐야함) 꼭꼭 씹어먹어야 체하지 않듯이, 배운 것들을 꼭꼭 잘근잘근 씹어서 온전한 내 것으로 만들어야지