blog

Java 참조 사양 강력한 소프트 참조

참조는 Java에서 객체를 조작하는 주요 방법입니다. 참조를 사용하면 프로그램에서 객체를 생성, 액세스 및 조작할 수 있습니다. Java의 참조에 대해 얼마나 알고 계신가요?...

Sep 3, 2025 · 6 min. read
シェア

소개

개념 및 역할

참조는 Java에서 객체를 조작하는 주요 방법이며, 참조를 통해 프로그램에서 객체를 생성, 액세스 및 조작할 수 있습니다.

java
Object obj = new Object();

여기서 객체는 새로 생성된 객체에 대한 참조입니다.

Java에는 여러 가지 유형의 참조가 있습니다:

  1. 강력한 참조
  2. 소프트 레퍼런스
  3. 약한 참조
  4. 더미 레퍼런스

각 참조 유형은 메모리 관리 및 쓰레기 수집 측면에서 서로 다른 특성과 용도를 가지고 있습니다.

Java 메모리 관리에서 참조의 중요성

메모리 관리는 모든 프로그래밍 언어와 그 운영 프레임워크에서 가장 중요한 기능 중 하나입니다. 메모리 관리에는 이 주소를 차지하고 있는 메모리가 프레임워크에 의해 회수되는지 어떻게 확인할 수 있을까요?

여기서 참조가 중요한 역할을 합니다. Java에서 메모리 관리는 개발자에게 맡겨져 있지 않고 JVM에 의해 체계적으로 관리됩니다. 가상 머신은 도달 가능성 알고리즘을 사용하여 객체가 여전히 참조되고 있는지 여부를 분석합니다. 참조는 객체에 도달할 수 있는지 여부를 판단하는 경로이며, 참조 없이 도달할 수 없는 경우 해당 객체를 회수할 수 있습니다.

간단한 예입니다:

java
public class Main { public static void main(String[] args) { Map<String, Object> map = new HashMap<>(); map = new TreeMap<>(); } }

코드의 세 번째 줄에서 HashMap 객체가 인스턴스화되었으므로 이제 힙에 객체가 차지하는 메모리 조각이 있고 맵이 객체를 참조합니다.

두 번째로 코드의 4번째 줄에서 맵이 재할당됩니다. 이 시점에서 방금 인스턴스화된 HashMap 객체는 어떤 변수나 객체에서도 참조되지 않으며 다음 쓰레기 수집에서 HashMap 객체가 재활용됩니다.

강력한 참조

정의 및 특징

Java에서는 일반적으로 변수나 객체가 객체를 참조할 때마다 둘 사이의 참조 관계를 강력한 참조라고 합니다.

java
public class Main { private static final Object OBJECT = new Object(); public static void main(String[] args) { Object obj = new Object(); } }

위 코드에서 객체와 객체는 모두 강력한 참조 관계를 가지고 있습니다.

메모리 관리 및 쓰레기 수집 동작

객체가 명시적으로 널로 선언되지 않은 경우, 프로그램이 실행 중이고 다른 객체에서 객체를 사용할 수 있는 한 프로그램이 종료될 때까지 가비지 수집할 수 없습니다. 프로그램이 종료될 때까지는 가비지 수집할 수 없습니다.

메서드 내에서 객체가 생성되면 도달 가능성 분석 알고리즘은 해당 객체가 다른 변수에 의해 참조되지 않는 한 메서드 실행이 끝날 때 null 객체로 간주합니다. 도달 가능성 분석 알고리즘은 객체에 대한 강력한 참조가 있더라도 객체를 되찾을 수 있다고 가정합니다.

소프트 레퍼런스

정의 및 사용법

SoftReference 클래스의 주석에는 클래스의 가장 중요한 역할이 언급되어 있습니다.

Soft references are most often used to implement memory-sensitive caches.

소프트 레퍼런스는 메모리 민감형 캐싱을 구현하는 데 가장 일반적으로 사용됩니다.

또한 댓글에 문장이 있습니다:

All soft references to softly-reachable objects are guaranteed to have been cleared before the virtual machine throws an OutOfMemoryError.

JVM이 OOM을 던지는 경우 참조된 객체에 대한 모든 소프트 참조가 지워집니다.

이 문은 또한 캐싱의 사용을 우회합니다.

소프트 참조를 만들고 사용하는 방법

java
public class Main { public static void main(String[] args) throws InterruptedException { Object object = new Object(); SoftReference<Object> softReference = new SoftReference<>(object); } }

SoftReference 객체를 생성하고 인스턴스화할 때 참조해야 하는 객체를 전달하기만 하면 됩니다.

메모리 부족 상황에서 소프트 참조를 위한 재활용 메커니즘

샘플 프로그램

시작 JVM 매개변수:

-Xms512m -Xmx512m

샘플 코드:

java
package com.zsk; import java.lang.ref.SoftReference; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Date; import java.util.List; public class Main { public static void main(String[] args) throws InterruptedException { int[] array = new int[(int) 1e7]; List<SoftReference<int[]>> data = new ArrayList<>(); SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); while (true) { int[] arr = new int[(int) 1e7]; Thread.sleep(1000L); SoftReference<int[]> softReference = new SoftReference<>(arr); System.out.println("Time: " + simpleDateFormat.format(new Date())); data.add(softReference); } } }

위 코드의 효과는 매초마다 큰 배열 객체가 생성되고 큰 배열 객체를 참조하는 소프트 참조 객체가 생성되며, 마지막으로 소프트 참조 객체가 목록에 배치된다는 것입니다.

메모리 변경

프로그램이 시작되면 아래와 같은 일반 그래프가 표시됩니다.

IntelliJ IDEA에서 생성

보기 경로: 프로파일러 -> 해당 프로세스 선택 -> CPU 및 메모리 실시간 차트

힙 메모리가 거의 소진될 때마다 JVM은 쓰레기 수집을 수행하며, 이 시점에서 소프트 참조가 참조하는 객체가 회수됩니다.

매번 큰 객체 배열을 만드는 데 필요한 메모리 공간의 양을 계산할 수도 있습니다:

10000000* 4= 40000000바이트 ≈ 38.15MB

이 양은 위의 메모리 증가 크기와 매립 후 공간과도 일치합니다.

메모리 분석

이는 MAT 메모리 분석 도구를 통해서도 확인할 수 있습니다.

첫째, 쓰레기 수집 전과 후의 메모리 스냅샷을 저장하기 위해 코드의 일부 처리가 필요합니다.

변경된 코드:

java
public class Main { public static void main(String[] args) throws InterruptedException { int[] array = new int[(int) 1e7]; List<SoftReference<int[]>> data = new ArrayList<>(); SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); int i = 0; while (true) { if (i++ == 10) { Thread.sleep(10000L); } if (i > 10) { Thread.sleep(10000L); } else { Thread.sleep(1000L); } int[] arr = new int[(int) 1e7]; SoftReference<int[]> softReference = new SoftReference<>(arr); System.out.println("Time: " + simpleDateFormat.format(new Date())); data.add(softReference); } } }

쓰레기 수거 변경 및 재활용 메커니즘

쓰레기 수집 전 메모리 풋프린트:

쓰레기 수집 후 메모리 풋프린트:

ArrayList의 모든 소프트 참조가 회수된 것을 명확하게 확인할 수 있으며, OOM이 발생하면 즉 힙 메모리가 모두 사용되면 약하게 참조된 참조가 모두 회수된다는 클래스에 대한 메모도 확인할 수 있습니다.

추가

비교 차트를 보면 ArrayList에서 약하게 참조된 객체의 주소가 왜 변경되었는지 의문이 들 수 있습니다.

JDK가 쓰레기 수집을 수행할 때 메모리 공간은 여러 쓰레기 수집기가 사용하는 쓰레기 수집 알고리즘에 따라 구성됩니다.

실행하기 전에 JVM 매개변수를 추가하여 사용되는 가비지 수집기를 출력할 수 있습니다.

JVM 시작 매개변수 추가: -Xloggc:. \gc.log

사용된 쓰레기 수집기 유형과 일부 시작 정보를 gc.log 파일에서 확인할 수 있습니다.

latex
OpenJDK 64-Bit Server VM (25.392-b08) for windows-amd64 JRE (1.8.0_392-b08), built on Oct 16 2023 22:02:46 by "Administrator" with MS VC++ 15.9 (VS2017) Memory: 4k page, physical 16726872k(8454788k free), swap 17775448k(3601532k free) CommandLine flags: -XX:InitialHeapSize=536870912 -XX:MaxHeapSize=536870912 -XX:+PrintGC -XX:+PrintGCTimeStamps -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:-UseLargePagesIndividualAllocation -XX:+UseParallelGC

샘플 코드에서는 실행 전에 러비시 수집기를 지정하지 않기 때문에 기본적으로 UseParallelGC 병렬 러비시 수집기를 사용합니다.

병렬 쓰레기 수집기는 마크 앤 카피 알고리즘을 사용합니다.

이 알고리즘은 세 단계를 수행합니다:

  1. 표시: 참조되지 않은 객체를 표시하며, 소프트 참조로만 참조되는 객체도 표시합니다.
  2. 복사: ParallelGC는 에덴과 영역에서 영역으로 살아있는 오브젝트를 복사합니다. 메모리 주소 변경도 이 복사 작업으로 인한 것입니다.
  3. 정리: ParallelGC는 더 이상 사용하지 않는 젊은 세대의 개체를 정리합니다.
Read next

tls 핸드셰이크 프로세스

"클라이언트 헬로" 메시지: 클라이언트는 서버가 선택할 수 있도록 클라이언트에서 지원하는 TLS 버전과 암호 조합이 포함된 "클라이언트 헬로" 메시지를 전송하여 서버와의 핸드셰이크 요청을 시작하고, 서버는 "클라이언트 r

Sep 2, 2025 · 2 min read