프로그래머스 문제를 풀다가 코드 실행 시간을 비교해 보고 싶었다.
📝 Java에서 성능 측정을 위한 헬퍼 클래스
작업을 여러 번 실행하여 평균 실행 시간을 측정하고 결과를 출력
제네릭을 사용하여 다양한 타입의 작업을 처리
package Java.helpers;
public class BenchmarkHelper {
/**
* 작업을 여러 번 실행하여 평균 실행 시간 측정하고 결과를 출력한다.
* @param name : 벤치마크 테스트 이름
* @param iteration : 작업 반복 횟수
* @param task : 벤치마크 테스트에서 실행할 작업
*/
public static <T> void runBenchmark (String name, int iteration, BenchmarkTask<T> task) {
long sum = 0;
T result = null;
for (int i=0; i<iteration; i++) {
long start = System.nanoTime();
result = task.run();
long end = System.nanoTime();
sum += end-start;
}
System.out.println(name + " 결과 : " + result);
System.out.println(name + " 실행 시간 : " + (sum/iteration) + "ns");
System.out.println("------------------------------------");
}
/**
* 벤치마크 테스트에서 실행할 작업 정의
*/
public interface BenchmarkTask<T> {
T run();
}
}
📝 BenchmarkHelper 클래스 사용 예제
import Java.helpers.BenchmarkHelper;
public class 괄호회전하기_test {
public static void main(String[] args) {
String s = "[](){}[](){}[](){}[](){}[](){}[](){}[](){}[](){}[](){}[](){}";
괄호회전하기 s1 = new 괄호회전하기();
괄호회전하기_2 s2 = new 괄호회전하기_2();
// 람다 표현식을 사용하여 BenchmarkTask 인터페이스의 run 메서드를 구현
BenchmarkHelper.runBenchmark("Solution 1", 10, ()->s1.solution(s));
BenchmarkHelper.runBenchmark("Solution 2", 10, ()->s2.solution(s));
}
}
📝 System.gc()
Java에서 가비지 컬렉션(Garbage Collection)을 명시적으로 요청하는 메서드이다
하지만 "요청"일 뿐, 반드시 실행된다는 보장은 없다
Java의 가비지 컬렉터(GC)에게 "지금 메모리 좀 청소해줘~"라고 요청하는 메서드이다
내부적으로는 Runtime.getRuntime().gc()를 호출한다
가비지 컬렉션(GC)
Java에서는 new 키워드로 만든 객체가 더 이상 사용되지 않으면 자동으로 메모리에서 제거된다
이걸 담당하는 것이 JVM의 가비지 컬렉터(Garbage Collector)이다
GC를 “실행해주면 좋겠다”라는 요청이어서 JVM이 무시할 수도 있다
성능을 떨어뜨릴 수 있기 때문에, 일반 애플리케이션에서 남용 금지
메모리 상태를 비우고 공평하게 만들기 위해 쓰기도 하지만, 오히려 왜곡된 결과가 나올 수도 있다
벤치마크 코드에서, 각 실행 전 메모리 초기화를 위해 시도해볼 수 있다
단, GC는 무겁기 때문에 오히려 벤치마크에 지장을 줄 수 있다
메모리 릭 테스트 중 GC 반응 확인용으로 시도한다
대부분 추천되지 않는다
JVM은 GC 실행 타이밍을 스스로 제어하고, 대부분의 경우 System.gc() 없이도 충분히 잘 관리한다
벤치마크용 warm-up 루틴으로 GC 대신 몇 번 미리 실행하고 평균을 재는 게 더 효과적이다
JVM의 메모리 구조
주요 영역
영역 | 설명 |
Heap | 객체가 생성되는 공간. GC의 대상이 되는 핵심 영역 |
Stack | 메서드 호출 시 지역 변수, 매개변수, 반환 주소 등이 저장됨 |
Method Area | 클래스 정보(메타데이터), static 변수 등 |
PC Register | 현재 실행 중인 명령어의 주소 저장 |
Native Method Stack | C/C++ 등 네이티브 코드 호출 시 사용 |
가비지 컬렉션은 Heap 영역에서만 동작한다
단계 | 설명 |
Mark | GC Root(스레드, static 변수 등)부터 시작해 접근 가능한 객체를 마킹 |
Sweep | 마킹되지 않은 객체를 삭제 |
Compact | 메모리 단편화를 막기 위해 남은 객체를 압축 정렬 |
Stop-the-World(STW)
가비지 컬렉션 중 JVM이 모든 애플리케이션 스레드를 멈추는 현상
GC 중에 다른 스레드가 메모리를 수정하면 일관성 문제가 생긴다
GC가 실행되는 동안 모든 작업을 멈춘다 → STW 발생
GC 알고리즘에 따라 STW 시간의 길이와 빈도가 다르다
System.gc()를 호출하면 JVM은 Full GC를 유도하는 경우가 많다
Full GC는 Old 영역을 정리하는 비용 높은 작업이라, STW 시간이 길어질 수 있다
👉 불필요하게 자주 호출하면 프로그램이 멈춘 것처럼 느려질 수 있다
- System.gc() : GC를 요청하지만 실행 보장은 없다
- GC 대상 : Heap에 존재하는 더 이상 참조되지 않는 객체들
- GC 과정 : Mark → Sweep → (Compact)
- STW : GC 수행 시 모든 애플리케이션 스레드 정지
'IT > JAVA' 카테고리의 다른 글
JMH (JMH 라이브러리를 활용한 Java 코드 성능테스트) (2) | 2025.04.11 |
---|---|
ArrayList vs LinkedList (0) | 2025.04.08 |
HashSet (0) | 2025.03.20 |
Pattern, Matcher Class (0) | 2025.02.25 |
StringBuilder (0) | 2025.01.19 |