인스턴스가 생성된 후 자동으로 실행하는 블록과 달리, 컴파일하자마자 생기는 블록
실행 순서를 보면 다음과 같다.
1) JRE 라이브러리 폴더에서 클래스를 찾는다.
2) 없으면, CLASSPATH 환경 변수에 지정된 폴더에서 클래스를 찾는다.
3) 찾았으면, 그 클래스 파일이 올바른 바이트코드인지 검증한다.
4) 올바른 바이트코드라면, Method Area 영역으로 파일을 로딩한다.
5) 클래스 블록이 있으면 순서대로 그 블록을 실행한다.
6) 클래스 안에 static block (스태틱 블록)들이 있으면 순서대로 그 블록을 실행한다.
public class CouponInfoDto {
public static int coin = 10; // 클래스 변수
static { // static 블록
coin = 11;
}
일반적으로 우리가 만든 Class는 Static 영역
에 생성되고, new 연산을 통해 생성한 객체는 Heap영역에 생성된다. 객체의 생성시에 할당된 Heap영역의 메모리는 Garbage Collector를 통해 수시로 관리를 받는다.
하지만 Static
키워드를 통해 Static 영역에 할당된 메모리는 모든 객체가 공유하는 메모리라는 장점과 객체를 생성하지 않고도 Static 자원에 접근이 가능하다는 것이다.
그중 static 클래스는 어떤식으로 사용되는지 알아보자.
static 클래스는 중첩 클래스(nested class)
를 사용할 때 사용한다. 다음 코드를 보자.
public class TestClass {
class NestedClass {
}
}
이런식으로 쓰면 다음 경로가 날아온다.
경고: Inner class may be 'static'
Reports any inner classes which may safely be made static. An inner class may be static if it doesn't reference its enclosing instance.
A static inner class does not keep an implicit reference to its enclosing instance. This prevents a common cause of memory leaks and uses less memory per instance of the class.
번역하면,
위험 없이 static 으로 만들수 있는 내부 클래스를 발견했습니다. 내부 클래스가 자신의 바깥 클래스 인스턴스를 참조하지 않는다면, 내부 클래스는 static으로 선언해도 됩니다.
static 내부 클래스는 자신의 바깥 클래스 인스턴스의 임시적 참조를 유지하지 않습니다. 즉 static 내부 클래스로 선언하면 메모리 누수의 일반적인 원인을 예방할 수 있고, 클래스의 각 인스턴스당 더 적은 메모리를 사용하게 됩니다.
이 경고는 다음과 같이 NestedClass를 static으로 선언
해주면 조건을 만족하여 다시 나타나지 않는다
public class TestClass {
/* ↓ */
static class NestedClass {
}
}
1) Inner class (Non-static nested class) 는 Outer class 의 인스턴스화 이후 Inner class 의 인스턴스화가 가능하며
2) 두 인스턴스의 관계정보는 Inner class의 인스턴스 안에 만들어져 메모리 공간을 더 차지하며, 생성시간도 더 걸린다.
3) 더 심각한 문제는 Inner class 가 Outer class 인스턴스에 대한 참조를 갖고 있기 때문에, Garbage Collection 은 Outer class 의 인스턴스를 수거 대상으로 보지 않아 GC 의 대상에서 빠지게 된다. (더 쉽게 풀어쓰자면 inner class, outer class 두 인스턴스가 연결되어 있어서 outer class 인스턴스의 메모리를 못 뺏는 것)
4) 때문에 Outer class 를 참조할 일이 없다면, Nested class 는 static 을 붙여 무조건 static nested class 를 만들자!
이는 이펙티브 자바 저바 조슈아 블로크도 강조한 바다.
멤버 클래스에서 바깥 인스턴스에 접근할 일이 없다면 무조건
static
을 붙여서 정적 멤버 클래스로 만들자. static을 생략하면 바깥 인스턴스로의 숨은 외부 참조를 갖게 된다. 앞서도 얘기했듯 이 참조를 저장하려면 시간과 공간이 소비된다. 더 심각한 문제는 가비지 컬렉션이 바깥 클래스의 인스턴스를 수거하지 못하는 메모리 누수가 생길 수 있다는 점이다(아이템 7). 참조가 눈에 보이지 않으니 문제의 원인을 찾기 어려워 때때로 심각한 상황을 초래하기도 한다.
이번 프로젝트에서 아주 요긴하게 사용했다.
public class ReservationCommand {
@Getter
@ToString
@NoArgsConstructor
public static class RegisterReservation { // static class로 정의
private Long userId;
private String startTime;
private String endTime;
@Builder
public RegisterReservation(Long userId, String startTime, String endTime) {
this.userId = userId;
this.startTime = startTime;
this.endTime = endTime;
}
public Reservation mapToEntity(User user, StudyGroup studyGroup, Room room) {
return Reservation.builder()
.startTime(TimeParsingUtils.formatterLocalDateTime(startTime))
.endTime(TimeParsingUtils.formatterLocalDateTime(endTime))
.user(user)
.studyGroup(studyGroup)
.room(room)
.build();
}
}
...
}
static 클래스는 Garbage Collector의 관리 영역 밖에 존재하므로 Static을 자주 사용하면 프로그램의 종료시까지 메모리가 할당된 채로 존재하므로 자주 사용하게 되면 시스템의 퍼포먼스에 악영향을 주게 되니
조심해서 사용해야 된다는 점 잊지말자!