1. 개요

이 짧은 사용방법(예제)에서는 Java에서 개체를 파괴할 수 있는 가능성을 살펴보겠습니다.

2. 자바의 소멸자

객체를 생성할 때마다 Java는 자동으로 힙에 메모리를 할당합니다. 마찬가지로 객체가 더 이상 필요하지 않을 때마다 메모리가 자동으로 할당 해제됩니다.

C와 같은 언어에서는 메모리에 있는 개체 사용을 마치면 수동으로 할당을 해제해야 합니다. 안타깝게도 Java는 수동 메모리 할당 해제를 지원하지 않습니다. 또한 Java 프로그래밍 언어의 기능 중 하나는 gar bage  collection 이라는 기술을 사용하여 자체적으로 개체 소멸을 처리하는 것입니다 .

3. 쓰레기 수거

가비지 수집은 힙의 메모리에서 사용되지 않는 개체를 제거합니다. 메모리 누수를 방지하는 데 도움이 됩니다. 간단히 말해서 특정 개체에 대한 참조가 더 이상 없고 해당 개체에 더 이상 액세스할 수 없는 경우 가비지 수집기는 이 개체를 연결할 수 없는 것으로 표시하고 해당 공간을 회수합니다.

가비지 수집을 제대로 처리하지 못하면 성능 문제가 발생할 수 있으며 결국 애플리케이션의 메모리가 부족해집니다.

개체는 프로그램에서 더 이상 액세스할 수 없는 상태에 도달하면 가비지 수집될 수 있습니다. 다음 두 가지 상황 중 하나가 발생하면 개체에 더 이상 연결할 수 없습니다.

  • 개체에 개체를 가리키는 참조가 없습니다.
  • 개체에 대한 모든 참조가 범위를 벗어났습니다.

Java에는 가비지 수집을 지원하는 데 도움 이 되는 System.gc() 메서드가 포함되어 있습니다. 이 메서드를 호출하여 JVM에 가비지 수집기를 실행하도록 제안할 수 있습니다. 그러나 JVM이 실제로 호출할 것이라고 보장할 수 없습니다. JVM은 요청을 무시할 수 있습니다.

4. 파이널라이저

Object 클래스는 finalize () 메서드를 제공합니다. 가비지 수집기가 메모리에서 개체를 제거하기 전에 finalize() 메서드를 호출합니다. 메서드는 0회 또는 1회 실행할 수 있습니다. 그러나 동일한 개체에 대해 두 번 실행할 수 없습니다.

Object 클래스 내부에 정의 된  finalize()  메서드 는 특별한 작업을 수행하지 않습니다.

종료자의 주요 목표는 개체가 메모리에서 제거되기 전에 개체에서 사용하는 리소스를 해제하는 것입니다. 예를 들어 데이터베이스 연결 또는 기타 리소스를 닫는 메서드를 재정의할 수 있습니다.

BufferedReader 인스턴스 변수 를 포함하는 클래스를 생성해 보겠습니다 .

class Resource {

    final BufferedReader reader;

    public Resource(String filename) throws FileNotFoundException {
        reader = new BufferedReader(new FileReader(filename));
    }

    public long getLineNumber() {
        return reader.lines().count();
    }
}
이 예에서는 리소스를 닫지 않았습니다. finalize() 메서드 내에서 닫을 수 있습니다 .
@Override
protected void finalize() {
    try {
        reader.close();
    } catch (IOException e) {
        // ...
    }
}

JVM이 finalize() 메서드를 호출하면 BufferedReader 리소스가 해제됩니다. finalize() 메서드 에서 발생하는 예외 는 개체 종료를 중지합니다.

그러나 Java 9부터 finalize() 메서드는 더 이상 사용되지 않습니다. finalize() 메서드를 사용하면 혼란스럽고 제대로 사용하기 어려울 수 있습니다.

개체가 보유한 리소스를 해제하려면 대신 AutoCloseable 인터페이스 구현을 고려해야 합니다 . CleanerPhantomReference 와 같은 클래스 는 개체에 연결할 수 없게 되면 리소스를 관리하는 보다 유연한 방법을 제공합니다.

4.1. AutoCloseable 구현

AutoCloseable 인터페이스는 try -with-resources 블록 을 종료할 때 자동으로 실행되는 close() 메서드를 제공합니다. 이 메서드 내에서 객체가 사용하는 리소스를 닫을 수 있습니다.

AutoCloseable 인터페이스 를 구현하도록 예제 클래스를 수정해 보겠습니다 .

class Resource implements AutoCloseable {

    final BufferedReader reader;

    public Resource(String filename) throws FileNotFoundException {
        reader = new BufferedReader(new FileReader(filename));
    }

    public long getLineNumber() {
        return reader.lines().count();
    }

    @Override
    public void close() throws Exception {
        reader.close();
    }
}

finalize() 메서드를 사용하는 대신 close() 메서드를 사용하여 리소스를 닫을 수 있습니다 .

4.2. 클리너 클래스

객체가 팬텀에 도달할 수 있을 때 특정 작업을 수행하려는 경우 Cleaner 클래스 를 사용할 수 있습니다. 즉, 개체가 종료되고 해당 메모리를 할당 해제할 준비가 된 경우입니다.

이제 Cleaner 클래스를 사용하는 방법을 살펴보겠습니다. 먼저 Cleaner 를 정의해 보겠습니다 .

Cleaner cleaner = Cleaner.create();

다음으로 더 깨끗한 참조를 포함하는 클래스를 만듭니다.

class Order implements AutoCloseable {

    private final Cleaner cleaner;

    public Order(Cleaner cleaner) {
        this.cleaner = cleaner;
    }
}

둘째, Order 클래스 내에서 Runnable 을 구현하는 정적 내부 클래스를 정의합니다.

static class CleaningAction implements Runnable {

    private final int id;

    public CleaningAction(int id) {
        this.id = id;
    }

    @Override
    public void run() {
        System.out.printf("Object with id %s is garbage collected. %n", id);
    }
}

내부 클래스의 인스턴스는 청소 작업을 나타냅니다. 개체가 팬텀에 도달할 수 있게 된 후 실행되도록 각 정리 작업을 등록해야 합니다.

청소 작업에 람다를 사용하지 않는 것을 고려해야 합니다. 람다를 사용하면 객체 참조를 쉽게 캡처하여 객체가 팬텀에 도달할 수 없게 되는 것을 방지할 수 있습니다. 위와 같이 정적 중첩 클래스를 사용하면 개체 참조가 유지되지 않습니다.

Order 클래스 내부에 Cleanable 인스턴스 변수를 추가해 보겠습니다 .

private Cleaner.Cleanable cleanable;

Cleanable 인스턴스 는 청소 작업이 포함된 청소 개체를 나타냅니다.

다음으로 청소 작업을 등록할 메서드를 만들어 보겠습니다.

public void register(Product product, int id) {
    this.cleanable = cleaner.register(product, new CleaningAction(id));
}

마지막으로 close() 메서드를 구현해 보겠습니다.

public void close() {
    cleanable.clean();
}

clean() 메서드 는 cleanable을 등록 취소하고 등록된 정리 작업을 호출합니다. 이 메서드는 clean 호출 횟수에 관계없이 최대 한 번만 호출됩니다.

try-with-resources 블록 내에서 CleaningExample 인스턴스 를 사용할 때 close() 메서드는 청소 작업을 호출합니다.

final Cleaner cleaner = Cleaner.create();
try (Order order = new Order(cleaner)) {
    for (int i = 0; i < 10; i++) {
        order.register(new Product(i), i);
    }
} catch (Exception e) {
    System.err.println("Error: " + e);
}

다른 경우 에는 인스턴스가 팬텀에 도달할 수 있게 되면 청소기가 clean() 메서드를 호출합니다.

또한 System.exit() 동안 클리너의 동작 은 구현에 따라 다릅니다. Java는 청소 작업이 호출되는지 여부를 보장하지 않습니다.

5. 결론

이 짧은 사용방법(예제)에서 우리는 Java에서 개체 소멸 가능성을 살펴보았습니다. 요약하면 Java는 수동 객체 소멸을 지원하지 않습니다. 그러나 finalize() 또는 Cleaner 를 사용하여 개체가 보유한 리소스를 해제할 수 있습니다. 항상 그렇듯이 예제의 소스 코드는 GitHub에서 사용할 수 있습니다 .
Generic footer banner