1. 소개
이 사용방법(예제)에서는 가비지 수집 이 정적 필드를 처리 하는 방법을 배웁니다. 또한 클래스 로딩 및 클래스 개체와 같은 주제를 다룰 것 입니다. 이 기사가 끝나면 클래스, 클래스 로더 및 정적 필드 간의 연결과 가비지 수집기가 이를 처리하는 방법을 더 잘 이해할 것입니다.
2. Java의 가비지 수집 개요
Java는 자동 메모리 관리라는 아주 좋은 기능을 제공합니다. 대부분의 경우 이 접근 방식은 매뉴얼만큼 효율적이지 않습니다. 그러나 디버그하기 어려운 문제를 피하고 상용구 코드를 줄이는 데 도움이 됩니다. 또한 가비지 컬렉션의 개선으로 프로세스가 점점 더 좋아지고 있습니다. 따라서 가비지 컬렉터가 작동하는 방식과 애플리케이션에 어떤 가비지가 있는지 검토해야 합니다.
2.1. 쓰레기 개체
참조 카운팅 은 가비지 개체를 식별하는 가장 간단하고 직관적인 방법입니다. 이 접근 방식을 사용하면 현재 객체에 대한 참조가 있는지 확인할 수 있습니다. 그러나 이 방법에는 몇 가지 단점이 있으며 가장 중요한 것은 순환 참조입니다.
순환 참조를 처리하는 방법 중 하나는 추적입니다. 객체는 애플리케이션의 가비지 수집 루트 에 대한 링크가 없으면 가비지로 변 합니다.
2.2. 정적 필드 및 클래스 개체
Java에서는 클래스 정의를 포함하여 모든 것이 Object 입니다. 여기에는 클래스, 메서드 및 정적 필드 값에 대한 모든 메타 정보가 포함됩니다. 따라서 모든 정적 필드는 존경받는 클래스 개체의 참조입니다. 따라서 클래스 개체가 존재하고 응용 프로그램에서 참조할 때까지 정적 필드는 가비지 수집에 적합하지 않습니다 .
동시에 로드된 모든 클래스에는 이 특정 클래스를 로드하는 데 사용되는 클래스 로더에 대한 참조가 있습니다. 이렇게 하면 로드된 클래스를 추적할 수 있습니다.
이 경우 참조 계층 구조가 있습니다. 클래스 로더는 로드된 모든 클래스에 대한 참조를 유지합니다. 동시에 클래스는 각 클래스 로더에 대한 참조를 저장합니다. 이 경우 양방향 참조가 있습니다. 새 객체를 인스턴스화할 때마다 클래스 정의에 대한 참조를 보유합니다. 따라서 다음과 같은 계층 구조가 있습니다.
애플리케이션이 참조할 때까지 클래스를 언로드할 수 없습니다. 가비지 수집에 적합한 클래스 정의를 만들기 위해 필요한 것이 무엇인지 확인해 봅시다. 첫째, 애플리케이션에서 클래스 인스턴스로의 참조가 없어야 합니다. 모든 인스턴스가 해당 클래스에 대한 참조를 포함하기 때문에 중요합니다. 둘째, 이 클래스의 클래스 로더는 애플리케이션에서 사용할 수 없어야 합니다. 마지막으로 클래스 자체는 애플리케이션에서 참조가 없어야 합니다.
3. 가비지 수집 정적 필드의 예
가비지 컬렉터가 정적 필드를 제거하도록 하는 예제를 만들어 봅시다. JVM은 확장 및 시스템 클래스 로더에 의해 로드된 클래스에 대한 클래스 언로딩을 지원합니다. 그러나 이것은 재현하기 어려울 것이며 이를 위해 사용자 정의 클래스 로더를 사용할 것입니다. 더 많은 제어가 가능하기 때문입니다.
3.1. 커스텀 클래스 로더
먼저 애플리케이션의 리소스 폴더에서 클래스를 로드할 자체 CustomClassloader 를 만들어 보겠습니다. 클래스 로더가 작동하려면 loadClass(String name) 메서드 를 재정의해야 합니다 .
public class CustomClassloader extends ClassLoader {
public static final String PREFIX = "com.baeldung.classloader";
public CustomClassloader(ClassLoader parent) {
super(parent);
}
@Override
public Class<?> loadClass(String name) throws ClassNotFoundException {
if (name.startsWith(PREFIX)) {
return getClass(name);
} else {
return super.loadClass(name);
}
}
...
}
이 구현에서는 리소스에서 클래스를 로드하는 복잡성을 숨기는 getClass 메서드를 사용하고 있습니다.
private Class<?> getClass(String name) {
String fileName = name.replace('.', File.separatorChar) + ".class";
try {
byte[] byteArr = IOUtils.toByteArray(getClass().getClassLoader().getResourceAsStream(fileName));
Class<?> c = defineClass(name, byteArr, 0, byteArr.length);
resolveClass(c);
return c;
} catch (IOException e) {
throw new RuntimeException(e);
}
}
3.2. 폴더 구조
올바르게 작동하려면 사용자 정의 클래스가 클래스 경로 범위 밖에 있어야 합니다. 이렇게 하면 시스템 클래스 로더 에 의해 업로드되지 않습니다 . 이 특정 클래스와 함께 작동하는 유일한 클래스 로더는 CustomClassloader 입니다.
폴더 구조는 다음과 같습니다.
3.3. 정적 필드 홀더
정적 필드 의 소유자 역할을 할 사용자 정의 클래스를 사용 합니다. 클래스 로더 구현을 정의한 후 이를 사용하여 준비한 클래스를 업로드할 수 있습니다. 간단한 클래스입니다.
public class GarbageCollectedStaticFieldHolder {
private static GarbageCollectedInnerObject garbageCollectedInnerObject =
new GarbageCollectedInnerObject("Hello from a garbage collected static field");
public void printValue() {
System.out.println(garbageCollectedInnerObject.getMessage());
}
}
3.4. 정적 필드 클래스
GarbageCollectedInnerObject 는 가비지로 바꾸고 싶은 객체를 나타냅니다. 이 클래스는 단순성과 편의성을 위해 GarbageCollectedStaticFieldHolder 와 동일한 파일에 정의되어 있습니다. 이 클래스에는 메시지가 포함되어 있으며 재정의 된 finalize() 메서드도 있습니다. finalize() 메서드는 더 이상 사용되지 않고 많은 단점이 있지만 가비지 수집기가 개체를 제거할 때 시각화할 수 있습니다. 이 방법은 프레젠테이션 목적으로만 사용합니다. 정적 필드 에 대한 클래스는 다음과 같습니다 .
class GarbageCollectedInnerObject {
private final String message;
public GarbageCollectedInnerObject(final String message) {
this.message = message;
}
public String getMessage() {
return message;
}
@Override
protected void finalize() {
System.out.println("The object is garbage now");
}
}
3.5. 수업 업로드
이제 클래스를 업로드하고 인스턴스화할 수 있습니다. 인스턴스를 생성한 후 클래스가 업로드되고 객체가 생성되었으며 정적 필드에 필요한 정보가 포함되어 있는지 확인할 수 있습니다.
private static void loadClass() {
try {
final String className = "com.baeldung.classloader.GarbageCollectedStaticFieldHolder";
CustomClassloader loader = new CustomClassloader(Main.class.getClassLoader());
Class<?> clazz = loader.loadClass(className);
Object instance = clazz.getConstructor().newInstance();
clazz.getMethod(METHOD_NAME).invoke(instance);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
이 메서드는 특수 클래스의 인스턴스를 만들고 메시지를 출력해야 합니다.
Hello from a garbage collected static field
3.6. 가비지 컬렉션 실행
이제 애플리케이션을 시작하고 가비지를 제거해 보겠습니다.
public static void main(String[] args) throws InterruptedException {
loadClass();
System.gc();
Thread.sleep(1000);
}
loadClass() 메서드를 호출한 후 이 메서드 내의 모든 변수, 즉 클래스 로더, 클래스 및 인스턴스가 범위를 벗어나 가비지 수집 루트와의 연결이 끊어집니다. 참조에 null 을 할당하는 것도 가능 하지만 범위를 사용하는 옵션이 더 깔끔합니다.
public static void main(String[] args) throws InterruptedException {
CustomClassloader loader;
Class<?> clazz;
Object instance;
try {
final String className = "com.baeldung.classloader.GarbageCollectedStaticFieldHolder";
loader = new CustomClassloader(GarbageCollectionNullExample.class.getClassLoader());
clazz = loader.loadClass(className);
instance = clazz.getConstructor().newInstance();
clazz.getMethod(METHOD_NAME).invoke(instance);
} catch (Exception e) {
throw new RuntimeException(e);
}
loader = null;
clazz = null;
instance = null;
System.gc();
Thread.sleep(1000);
}
이 코드에 몇 가지 문제가 있지만 대부분의 경우 작동합니다. 주요 문제는 Java에서 가비지 수집을 강제할 수 없으며 System.gc() 호출 이 차고 수집이 발생한다고 보장하지 않는다는 것입니다. 그러나 대부분의 JVM 구현에서 이는 Major Garbage Collection 을 트리거 합니다. 따라서 출력에 다음 줄이 표시되어야 합니다.
Hello from a garbage collected static field
The object is garbage now
이 출력은 가비지 수집기가 정적 필드를 제거했음을 보여줍니다. 가비지 수집기는 또한 클래스 로더, 홀더의 클래스, 정적 필드 및 연결된 개체를 제거했습니다.
3.7. System.gc()가 없는 예
또한 보다 자연스럽게 가비지 수집을 트리거할 수 있습니다. 이 방법은 더 안정적으로 작동합니다. 그러나 가비지 수집기를 호출하려면 더 많은 주기가 필요합니다.
public static void main(String[] args) {
while (true) {
loadClass();
}
}
여기서는 동일한 loadClass() 메서드를 사용하고 있지만 System.gc() 를 호출하지 않고 무한 루프에서 클래스를 로드하기 때문에 메모리가 부족하면 가비지 수집기가 트리거됩니다.
4. 결론
이 기사에서는 클래스 및 정적 필드와 관련하여 Java에서 가비지 수집이 작동하는 방식을 설명했습니다. Custom형 클래스 로더를 생성하여 예제에 사용했습니다. 또한 클래스 로더, 클래스 및 해당 정적 필드 간의 연결을 배웠습니다. 추가 이해를 위해 텍스트에 연결된 기사를 살펴볼 가치가 있습니다.
코드는 GitHub에서 사용할 수 있습니다 .