1. Agent 부팅 흐름
전체 흐름
Target JVM
-> PinpointBootStrap.premain(String, Instrumentation)
-> PinpointBootStrap.start()
-> PinpointStarter.start()
-> AgentBootLoader.boot(AgentOption)
-> new DefaultAgent(Map<String, Object>)
-> DefaultAgent.start()
-> DefaultApplicationContext.start()
큰 그림으로 보면 bootstraps/bootstrap은 JVM에 가장 먼저 붙는 얇은 부트스트랩 계층이고, 실제 agent runtime은 agent-module/profiler 아래의 DefaultAgent와 DefaultApplicationContext에서 시작된다.
1.1 JVM 진입점
파일:
agent-module/bootstraps/bootstrap/src/main/java/com/navercorp/pinpoint/bootstrap/PinpointBootStrap.java
핵심 메소드:
premain(String agentArgs, Instrumentation instrumentation)start()
premain()은 JVM이 -javaagent 옵션으로 agent를 로딩할 때 호출하는 표준 Java Agent 진입점이다.
premain()
-> DisableOptions.isBootDisabled()
-> LoadState.start()
-> new PinpointBootStrap(agentArgs, instrumentation)
-> start()
여기서 하는 일은 많지 않다.
- agent disable 여부 확인
- 중복 시작 방지
- JVM이 넘겨준
Instrumentation객체 보관 - 실제 시작 로직으로 위임
start()는 agent 설치 디렉터리를 찾고 bootstrap jar를 JVM bootstrap classloader에 추가한다.
start()
-> JavaAgentPathResolver.resolveJavaAgentPath()
-> ArgsParser.parse(agentArgs)
-> AgentDirBaseClassPathResolver.resolve()
-> appendToBootstrapClassLoader(...)
-> new PinpointStarter(...)
-> PinpointStarter.start()
중요한 메소드:
appendToBootstrapClassLoader(Instrumentation, BootDir)
이 메소드에서 instrumentation.appendToBootstrapClassLoaderSearch(jarFile)을 호출한다. Pinpoint가 계측 대상 애플리케이션보다 더 낮은 classloader 영역에 공용 bootstrap 클래스를 올리는 지점이다.
백엔드 관점으로 보면 이 단계는 Spring Boot의 main()보다 더 앞단이다. 애플리케이션 코드가 뜨기 전에 agent가 먼저 자기 실행 환경을 준비한다.
1.2 Agent classloader 생성
파일:
agent-module/bootstraps/bootstrap/src/main/java/com/navercorp/pinpoint/bootstrap/PinpointStarter.java
핵심 메소드:
start()loadProperties()resolveLib(AgentDirectory)createClassLoader(String, URL[], ClassLoader, Properties)createAgentOption(...)
PinpointStarter.start()는 실제 profiler jar들을 로딩할 agent 전용 classloader를 만든다.
PinpointStarter.start()
-> loadProperties()
-> DisableOptions.isDisabled(...)
-> resolveLib(agentDirectory)
-> createClassLoader("pinpoint.agent", urls, parentClassLoader, properties)
-> loadModuleBootLoader(...)
-> new AgentBootLoader(agentType.getClassName(), agentClassLoader)
-> createAgentOption(...)
-> agentBootLoader.boot(option)
-> agent.start()
-> agent.registerStopHandler()
여기서 만들어지는 AgentOption에는 agent runtime에 필요한 값들이 들어간다.
Instrumentation- config
Properties - agent path
- plugin jar 목록
- bootstrap jar path 목록
agent-module/agent는 실제 Java 코드보다 배포 리소스와 설정을 담는 패키징 모듈에 가깝다. 실제 실행 코드는 agent-module/profiler에 있다.
1.3 DefaultAgent 생성
파일:
agent-module/bootstraps/bootstrap/src/main/java/com/navercorp/pinpoint/bootstrap/AgentBootLoader.javaagent-module/profiler/src/main/java/com/navercorp/pinpoint/profiler/DefaultAgent.java
핵심 메소드:
AgentBootLoader.boot(AgentOption)DefaultAgent(Map<String, Object>)
AgentBootLoader.boot()는 agent classloader로 com.navercorp.pinpoint.profiler.Agent 인터페이스와 실제 구현 클래스를 로딩한다.
AgentBootLoader.boot()
-> loadClass("com.navercorp.pinpoint.profiler.Agent")
-> loadClass(bootClass)
-> bootStrapClazz.getDeclaredConstructor(Map.class)
-> newInstance(agentOption.toMap())
기본 agent type에서는 bootClass가 DefaultAgent 계열로 연결된다.
DefaultAgent 생성자는 agent runtime의 기본 환경을 만든다.
new DefaultAgent(map)
-> AgentOption.of(map)
-> ProfilerConfigLoader.load(properties)
-> log config 준비
-> ObjectName 생성
-> AgentSystemConfig 저장
-> Log4j2LoggingSystem.start()
-> AgentContextOptionBuilder.build(...)
-> 상태 INITIALIZING
-> newApplicationContext(agentContextOption)
중요한 객체:
AgentOption: bootstrap 계층에서 넘어온 원본 실행 옵션ProfilerConfig:pinpoint.config계열 설정을 읽은 agent 설정 객체ObjectName: agentId, agentName, applicationName, serviceName 등 agent identityAgentContextOption:DefaultApplicationContext를 만들기 위한 정리된 옵션
백엔드 관점으로 보면 DefaultAgent 생성자는 서버 앱의 ApplicationContext 생성 직전까지 logging, config, identity를 준비하는 bootstrap layer다.
1.4 ApplicationContext 생성과 transformer 등록
파일:
agent-module/profiler/src/main/java/com/navercorp/pinpoint/profiler/DefaultAgent.javaagent-module/profiler/src/main/java/com/navercorp/pinpoint/profiler/context/module/DefaultApplicationContext.javaagent-module/profiler/src/main/java/com/navercorp/pinpoint/profiler/context/module/ApplicationContextModuleFactory.java
핵심 메소드:
DefaultAgent.newApplicationContext(AgentContextOption)DefaultApplicationContext(AgentContextOption, ModuleFactory)ApplicationContextModuleFactory.newModule(AgentContextOption)
DefaultAgent.newApplicationContext()는 Guice module factory를 찾고 DefaultApplicationContext를 만든다.
newApplicationContext()
-> profilerConfig.readString("profiler.guice.module.factory", null)
-> new DefaultModuleFactoryResolver(factoryClazzName)
-> moduleFactoryResolver.resolve()
-> new DefaultApplicationContext(agentOption, moduleFactory)
ApplicationContextModuleFactory.newModule()은 agent runtime에 필요한 Guice module을 합친다.
newModule()
-> ConfigModule
-> PluginModule
-> ApplicationContextModule
-> GrpcModule
-> StatsModule
-> ThriftStatsModule
-> ExceptionTraceModule or DisabledExceptionTraceModule
-> ErrorRecorderModule
-> MicrometerModule
DefaultApplicationContext 생성자에서 중요한 일이 일어난다.
DefaultApplicationContext(...)
-> Guice.createInjector(...)
-> injector.getInstance(ProfilerConfig.class)
-> injector.getInstance(InstrumentEngine.class)
-> injector.getInstance(ClassFileTransformer.class)
-> injector.getInstance(DynamicTransformTrigger.class)
-> instrumentation.addTransformer(classFileTransformer, true)
-> RPC-MODULE lifecycle start
-> TraceContext 조회
-> AgentInfoSender / AgentStatMonitor / MicrometerMonitor 조회
핵심 포인트:
ClassFileTransformer등록은DefaultApplicationContext.start()가 아니라 생성자에서 이미 일어난다.- Java 9 이상이면 module 처리를 위해
ClassFileTransformerModuleHandler와ClassFileTransformerModuleWrap을 거친다. - bytecode dump 옵션이 켜져 있으면
BytecodeDumpTransformer.wrap(...)으로 transformer를 감싼다.
즉 "Pinpoint agent가 애플리케이션 코드를 계측할 준비가 됐다"는 것은 instrumentation.addTransformer(...)가 성공했다는 뜻이다.
1.5 Agent start와 shutdown hook
파일:
agent-module/profiler/src/main/java/com/navercorp/pinpoint/profiler/DefaultAgent.javaagent-module/profiler/src/main/java/com/navercorp/pinpoint/profiler/context/module/DefaultApplicationContext.java
핵심 메소드:
DefaultAgent.start()DefaultAgent.registerStopHandler()DefaultAgent.close()DefaultApplicationContext.start()DefaultApplicationContext.close()
DefaultAgent.start()는 상태를 바꾸고 application context를 시작한다.
DefaultAgent.start()
-> INITIALIZING 상태 확인
-> RUNNING으로 변경
-> applicationContext.start()
-> printBanner()
DefaultApplicationContext.start()는 모니터와 agent 정보 전송을 시작한다.
DefaultApplicationContext.start()
-> deadlockMonitor.start()
-> agentInfoSender.start()
-> agentStatMonitor.start()
-> micrometerMonitor.start()
종료 처리는 registerStopHandler()에서 JVM shutdown hook으로 등록된다.
registerStopHandler()
-> ShutdownHookRegisterProvider.get()
-> new Thread(DefaultAgent.this.close())
-> shutdownHookRegister.register(shutdownThread)
close()에서는 반대로 sender와 monitor를 멈추고 RPC module을 shutdown한다.
DefaultApplicationContext.close()
-> agentInfoSender.stop()
-> micrometerMonitor.stop()
-> agentStatMonitor.stop()
-> deadlockMonitor.stop()
-> rpcModuleLifeCycle.shutdown()
Agent 부팅 문제를 디버깅할 때 보는 순서
PinpointBootStrap.premain()에 진입했는가?DisableOptions때문에 early return하지 않았는가?LoadState.start()가 중복 시작으로 막지 않았는가?JavaAgentPathResolver가 agent path를 제대로 찾았는가?AgentDirBaseClassPathResolver.resolve()가 agent directory를 검증했는가?appendToBootstrapClassLoader()가 bootstrap jar를 append했는가?PinpointStarter.createClassLoader()가pinpoint.agentclassloader를 만들었는가?AgentBootLoader.boot()가DefaultAgent를 생성했는가?DefaultApplicationContext생성 중instrumentation.addTransformer(...)가 호출됐는가?DefaultApplicationContext.start()에서 sender와 monitor가 시작됐는가?
'개발 메모' 카테고리의 다른 글
| Pinpoint 분석 #5 (0) | 2026.08.04 |
|---|---|
| Pinpoint 분석 #4 (0) | 2026.08.04 |
| Pinpoint 분석 #2 (0) | 2026.08.04 |
| Pinpoint 분석 #1 (0) | 2026.08.04 |
| 멀티 스레딩에서 자원 공유하기 (0) | 2024.11.16 |