라떼군 이야기
Java 21 업그레이드 후 발생하는 'A Java agent has been loaded dynamically' 경고 해결 방법
Problem
Java 17에서 Java 21로 JDK 버전을 업그레이드한 후, IntelliJ나 빌드 도구에서 JUnit 테스트를 실행할 때 다음과 같은 경고 로그가 발생하는 상황입니다.
WARNING: A Java agent has been loaded dynamically (C:\...\byte-buddy-agent-1.14.9.jar)
WARNING: If a serviceability tool is in use, please run with -XX:+EnableDynamicAgentLoading to hide this warning
WARNING: If a serviceability tool is not in use, please run with -Djdk.instrument.traceUsage for more information
WARNING: Dynamic loading of agents will be disallowed by default in a future release
이 문제는 주로 Mockito와 같은 테스팅 라이브러리가 내부적으로 Byte Buddy를 사용하여 실행 중인 JVM에 에이전트를 동적으로 로드하려고 할 때 발생합니다.
Background
이 경고는 Java 21에 도입된 JEP 451: Prepare to Disallow the Dynamic Loading of Agents로 인해 발생합니다. Java Agent는 실행 중인 애플리케이션의 바이트코드를 수정할 수 있는 강력한 도구입니다. 기존에는 실행 중인 JVM에 에이전트를 동적으로 로드하는 것이 허용되었으나, 이는 무결성 및 보안 문제를 야기할 수 있습니다.
따라서 Java 21부터는 에이전트의 동적 로딩 시 경고를 발생시키며, 향후 버전에서는 이를 기본적으로 차단할 예정입니다. Mockito는 Mock 객체를 생성하기 위해 Byte Buddy 에이전트를 동적으로 로드하는 방식을 사용해왔기 때문에 이 변화의 영향을 받게 되었습니다.
Solution
이 문제를 해결하는 방법은 크게 두 가지가 있습니다. 경고를 숨기는 방법(단기적 해결)과 에이전트를 정적으로 로드하는 방법(권장되는 장기적 해결)입니다.
방법 1: 경고 숨기기 (JVM 옵션 추가)
가장 간단한 방법은 JVM 옵션에 -XX:+EnableDynamicAgentLoading을 추가하여 동적 로딩을 명시적으로 허용하는 것입니다.
Maven Surefire Plugin 설정 (pom.xml):
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>3.2.2</version>
<configuration>
<!-- 동적 에이전트 로딩을 허용하여 경고를 숨깁니다 -->
<argLine>-XX:+EnableDynamicAgentLoading</argLine>
</configuration>
</plugin>
IntelliJ Run Configuration 설정:
IntelliJ에서 개별 테스트 실행 시에도 경고를 없애려면, Run/Debug Configurations 템플릿(JUnit)의 VM Options에 -XX:+EnableDynamicAgentLoading을 추가해야 합니다.
방법 2: Java Agent 정적 로딩 (권장)
가장 확실한 해결책은 에이전트를 동적으로 로드하지 않고, 애플리케이션 시작 시점에 -javaagent 옵션을 통해 로드하는 것입니다. Mockito의 경우 mockito-core를 에이전트로 지정하면 됩니다.
Maven 설정 (pom.xml):
maven-dependency-plugin을 사용하여 에이전트 jar 파일의 경로를 속성으로 저장하고, 이를 maven-surefire-plugin에 전달합니다.
<build>
<plugins>
<!-- 1. mockito-core jar 파일의 경로를 속성에 저장 -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<executions>
<execution>
<goals>
<goal>properties</goal>
</goals>
</execution>
</executions>
</plugin>
<!-- 2. 테스트 실행 시 javaagent 옵션으로 전달 -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<!-- maven-dependency-plugin이 생성한 속성을 사용 -->
<argLine>@{argLine} -javaagent:${org.mockito:mockito-core:jar}</argLine>
</configuration>
</plugin>
</plugins>
</build>
이 방법을 사용하면 JEP 451의 권장 사항을 따르게 되어 향후 Java 버전에서도 문제없이 동작합니다.
Deep Dive
IntelliJ ‘Run’ 버튼과 빌드 도구의 차이
Maven이나 Gradle 명령어로 테스트를 실행할 때는 pom.xml이나 build.gradle의 설정이 적용되지만, IntelliJ의 에디터 좌측 녹색 화살표(Run 버튼)를 눌러 실행할 때는 IDE가 자체적으로 생성한 명령어를 사용합니다. 따라서 pom.xml에 설정을 추가했더라도 IDE 실행 시에는 여전히 경고가 뜰 수 있습니다.
이를 해결하려면 IntelliJ의 Run/Debug Configurations > Edit Configuration Templates > JUnit으로 이동하여 VM Options에 다음을 추가해야 합니다.
-javaagent:$MAVEN_REPOSITORY$/org/mockito/mockito-core/5.14.2/mockito-core-5.14.2.jar
하지만 이 방법은 로컬 경로와 버전을 하드코딩해야 하는 단점이 있습니다. 따라서 팀 프로젝트에서는 pom.xml 설정을 통해 CI/CD 환경에서의 안정성을 확보하고, 로컬 IDE에서는 -XX:+EnableDynamicAgentLoading을 템플릿에 추가하여 경고를 무시하는 방식이 현실적인 타협안일 수 있습니다.
Conclusion
Java 21의 동적 에이전트 로딩 경고는 보안 강화를 위한 조치입니다. 당장은 -XX:+EnableDynamicAgentLoading 옵션으로 경고를 끌 수 있지만, 이는 임시방편일 뿐입니다. 장기적으로는 빌드 스크립트에서 -javaagent를 사용하여 에이전트를 시작 시점에 로드하도록 설정을 변경하는 것이 좋습니다. 이를 통해 향후 Java 버전에서의 호환성 문제를 미리 예방할 수 있습니다.