새로 알게된 사실이나, 중요하다고 생각하는 개념 위주로 정리해보았습니다.
JVM 구조
1. 메소드 영역(=클래스 영역, static영역)
- 메소드 영역, 클래스 파일의 바이트 코드가 로드되는 곳으로 JVM이 어떤 것들을 실행하려면 그 바이트 코드들이 메모리 공간에 저장이 되어있어야 합니다. JVM은 자바 프로그램에서 특정 클래스가 사용되면 해당 클래스의 클래스 파일을 읽어들여, 해당 클래스에 대한 정보를 메소드 영역에 저장합니다. 그 클래스의 변수도 이 영역에 저장됩니다.
2. 힙영역
- 인스턴스가 생성되는 공간으로, 프로그램 실행 중 생성되는 인스턴스는 모두 이곳에 생성됩니다. 즉 인스턴스 변수들이 생성되는 공간입니다.
3. 스택영역
- 이 메모리는 메서드가 작업을 수행하는 동안 지역변수들과 연산의 중간결과 등을 저장하는데 사용되며, 메소드가 작업을 마치면 할당되어있던 메모리 공간은 반환되어 비워집니다. GC의 대상입니다.
-> 더 자세한 사항은,
여기를 참고해주세요.
변수의 종류
변수의 종류 | 선언 위치 | 생성 시기 | 저장 위치 |
클래스 변수 | 클래스 영역 | 클래스가 메모리에 올라갔을 때 | static 메모리에 생성 프로그램 실행시 생성되고, 종료되면 소멸 |
인스턴스 변수 | 인스턴스가 생성되었을때 | heap메모리에 생성되고, GC에 의해 소멸됩니다. |
|
지역 변수 | 클래스 영역 이외의 영역, 메서드, 생성자, 초기화 블럭내부 | 변수 선언문이 수행되었을때 | stack 메모리에 생성됨 초기화 후 사용가능 메소드 종료시 메모리에서 소멸 |
변수는 클래스변수, 인스턴스변수, 지역변수 세 종류가 있습니다.
1. 클래스 변수:
1-1.클래스 변수를 선언하기 위해서는 인스턴스 변수 앞에 static키워드를 붙이면 됩니다.
1-2. 클래스 변수는 모든 인스턴스가 공통된 저장공간(static 메모리)을 공유하게 됩니다.
한 클래스들의 모든 인스턴스들이 공통적인 값을 유지해야하는 경우 클래스 변수로 선언해야합니다.
1-3. 클래스 변수는 인스턴스 변수와 달리 인스턴스를 생성하지 않고도, 언제라도 바로 사용할 수 있다는 특징을 지니며 클래스이름.클래스변수와 같은 형식으로 사용합니다.
2. 인스턴스 변수 :
2-1.클래스 영역에 선언되며, 클래스의 인스턴스를 생성할 때 만들어집니다.
2-2. 인스턴스 변수의 값을 읽어 오거나, 저장하기 위해서는 먼저 인스턴스를 생성해야 합니다.
2-3. 인스턴스는 독립적인 저장공간을 가지므로 서로 다른 값을 가질 수 있습니다.
3. 지역변수:
3-1. 메서드 내에 선언되어 메서드 내에서만 사용가능하며, 메서드가 종료되면 소멸되어 사용할 수 없게 됩니다.
3-2. stack 메모리에 생성됩니다.
논의점1. 클래스 멤버가 인스턴스 멤버를 자유롭게 호출할 수 있을까?
p.280 -> 인스턴스 멤버가 존재하는 시점에 클래스 멤버는 항상 존재하지만, 클래스 멤버가 존재하는 시점에는 인스턴스 멤버가 존재하지 않을 수 있기 때문에 클래스 멤버가 인스턴스 멤버를 참조 또는 호출하려고 할때 인스턴스를 생성해야한다.
package ch6;
import java.lang.reflect.Member;
class MemberCall {
int iv = 10;
static int cv = 20;
int iv2 = cv;
//static int cv2 = iv; // 클래스 변수는 인스턴스 변수를 활용할 수 없다
static int cv3 = new MemberCall().iv;
static void staticMethod1(){
System.out.println(cv);
//클래스 메서드에서는 인스턴스 변수 사용불가
//System.out.println(iv);
MemberCall c = new MemberCall();
System.out.println(c.iv);
}
void instanceMethod1(){
System.out.println(cv);
System.out.println(iv); // 인스턴스 메서드에서는 인스턴스 변수를 사용가능
}
static void staticMethod2(){
staticMethod1();
//컴파일 에러. 클래스 메서드에서는 인스턴스 메서드 호출 불가
//instanceMethod1();
MemberCall c = new MemberCall();
c.instanceMethod1();
}
void instanceMethod2(){
staticMethod1();
instanceMethod1();
}
}
위의 정리한 내용에 따르면, 인스턴스가 생성되는 시점에는 항상 클래스가 메모리에 로딩되어 있음이 보장되어 있습니다.
즉 클래스가 존재하지 않는데 클래스의 인스턴스를 논리적으로 생성할 수 없기에, 인스턴스 변수는 클래스 변수나 클래스 메서드를 호출할 수 있지만 그 역은 성립하지 않습니다.
생성자
생성자는 인스턴스가 생성될 때 호출되는 '인스턴스 초기화 메서드'입니다.
생성자의 조건은 다음과 같습니다.
1. 생성자의 이름은 클래스의 이름과 같아야 합니다.
2. 생성자는 리턴값이 없습니다.
모든 클래스에는 반드시 하나 이상의 생성자가 정의되어있으며, 그래야만 합니다. 생성자를 정의하지 않고도 인스턴스를 생성할 수 있었던 이유는 - 컴파일러가 생성자가 따로 정의되어있지 않은 경우 기본 생성자를 추가하여 컴파일 해주기 때문입니다. 그러나, 다음 예제를 보면 그 예외가 드러납니다.
class Data1{
int value;
}
class Data2{
int value;
Data2(int x) { // 매개변수가 있는 생성자
value = x;
}
}
class ConstructorTest {
public static void main(String[] args) {
Data1 d1 = new Data1();
Data2 d2 = new Data2(); // compile error
}
}
Data d2 = new Data2();
위 처럼 기본 생성자를 사용하여 컴파일하면 바로 컴파일 에러가 나타납니다.
이러한 컴파일 에러가 나타나는 이유는, Data2에는 사용자가 정의한 생성자 Data2(int x)가 정의되어 있기 때문에 기본 생성자가 추가되지 않았기 때문입니다.
기본 생성자가 컴파일러에 의해서 추가되는 경우는 클래스에 정의된 생성자가 하나도 없을 때뿐입니다.
생성자를 이용한 인스턴스의 복사
class Car {
String color;
String gearType;
int door;
Car(){
this("write","auto",4);
}
Car(Car c){
color = c.color;
gearType = c.gearType;
door = c.door;
}
Car(String color, String gearType, int door){
this.color = color;
this.gearType = gearType;
this.door = door;
}
}
class CarTest3{
public static void main(String[] args){
Car c1 = new Car();
Car c2 = new Car(c1);
System.out.println("c1의 color ="+ c1.color + ", gearType=" + c1.gearType + ", door=" + c1.door);
System.out.println("c2의 color ="+ c2.color + ", gearType=" + c2.gearType + ", door=" + c2.door);
c1.door = 100;
System.out.println("c1.door =100; 수행후");
System.out.println("c1의 color=" + c1.color + ", gearType=" + c1.gearType + ", door= "+c1.door);
System.out.println("c2의 color =" + c2.color + ", gearType=" + c2.gearType + ", door = " + c2.door);
}
}
c2는 c1을 복사해서 생성되었으므로 같은 상태를 지니지만, 서로 다른 메모리 공간에 존재하는 별도의 인스턴스이므로 c1의 값들이 변경되어도 c2는 영향을 받지 않습니다.
변수의 초기화
변수를 선언하고 처음으로 값을 저장하는 것을 '변수의 초기화'라고 합니다. 이러한 변수의 초기화 방법은 보통 세가지,명시적 초기화, 생성자, 초기화 블록으로 나눠집니다.
1. 명시적 초기화 - 변수를 선언과 동시에 초기화하는 것을 명시적 초기화라고 합니다. 가장 기본적이면서도 간단한 초기화 방법입니다.
class Car{
int door = 4; // 기본형 변수의 초기화
Engine e = new Engine(); // 참조형 변수의 초기화
}
2. 초기화 블럭 - 클래스 초기화 블럭은 클래스 변수의 초기화에 사용되고, 인스턴스 초기화 블럭은 인스턴스 변수의 초기화에 사용됩니다.
class BlockTest{
// 클래스 초기화 블럭
static{
System.out.println("static { }");
}
//인스턴스 초기화 블럭
{
System.out.println("{ }");
}
public BlockTest(){
System.out.println("생성자");
}
public static void main(String[] args){
BlockTest bt = new BlockTest();
BlockTest bt2 = new BlockTest();
}
}
클래스 초기화 블럭이 가장 먼저 수행되어 static {}이 출력되고, 다음으로 main메서드가 실행되어 BlockTst인스턴스가 실행되면서 인스턴스 초기화 블럭이 실행되고, 생성자가 그 다음으로 실행됩니다.
즉 클래스 초기화 블럭은 클래스가 메모리에 처음 로딩될때 수행되며, 인스턴스 초기화 블럭은 생성자와 같이 인스턴스를 생성할때마다 수행됩니다.
'Language > Java' 카테고리의 다른 글
예외처리 (2) | 2021.12.12 |
---|---|
객체지향 프로그래밍2 (0) | 2021.12.05 |
직렬화와 역직렬화 (0) | 2021.11.17 |
Java Checked Excpetion, UnChecked Exception (0) | 2021.09.26 |
자바 - 동일성과 동등성(equals, == ) (0) | 2021.09.11 |