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 입니다.

폴더 구조는 다음과 같습니다.

스크린샷-2022-09-05-at-10.46.21

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에서 사용할 수 있습니다 .

Generic footer banner