오늘은 어디로 갈까...

Instrumentation 본문

無汗不成

Instrumentation

剛宇 2009. 5. 8. 17:21

 JavaRebel은 변경 사항들을 "on-the-fly" 방식으로 재로딩가능하게 한다~~~고 한다. ^^; 어떤 원리로 작동하는지 궁금해서 그 비밀을 파헤쳐보려고 했으나, 내공 부족으로 실패... 다음을 기약하며, 그 흔적을 남긴다.

 1.4는 무시해버리고, 1.5를 기준으로 파보았다.
 일단 시작은, JDK 1.5부터 지원하는 java.lang.instrument 패키지를 이용해서 Agent를 실행시킨다.
 자바를 실행할때 -javaagent를 옵션을 줘서 실행하면, 해당 Agent를 실행시킬 수 있다.

java -javaagent:agent.jar=args TestMain

 이 Agent Class는 일반적인 어플리케이션의 진입 포인트(Entry Point)인 main() 메소드처럼, premain()이란 메소드를 구현해야한다.

public static void premain(String agentArgument, Instrumentation instrumentation) {
	// 어쩌구저쩌구
}

agentArgument 는 -javaagent:agent.jar=args 부분의 args가 넘어오게 된다. instrumentation는 자동(?)으로 넘어오므로, 잘 사용해주면 된다.
자세한 사항은 http://java.sun.com/javase/6/docs/api/java/lang/instrument/package-summary.html를 참고 바란다.
주의할것은 jar를 만들때, MANIFEST.MF를 포함시켜야하고 필수(?) 정보를 넣어야한다.


Manifest Attributes
The following manifest attributes are defined for an agent JAR file:

  • Premain-Class : When an agent is specified at JVM launch time this attribute specifies the agent class. That is, the class containing the premain method. When an agent is specified at JVM launch time this attribute is required. If the attribute is not present the JVM will abort. Note: this is a class name, not a file name or path.
  • Agent-Class : If an implementation supports a mechanism to start agents sometime after the VM has started then this attribute specifies the agent class. That is, the class containing the agentmain method. This attribute is required, if it is not present the agent will not be started. Note: this is a class name, not a file name or path.
  • Boot-Class-Path :  list of paths to be searched by the bootstrap class loader. Paths represent directories or libraries (commonly referred to as JAR or zip libraries on many platforms). These paths are searched by the bootstrap class loader after the platform specific mechanisms of locating a class have failed. Paths are searched in the order listed. Paths in the list are separated by one or more spaces. A path takes the syntax of the path component of a hierarchical URI. The path is absolute if it begins with a slash character ('/'), otherwise it is relative. A relative path is resolved against the absolute path of the agent JAR file. Malformed and non-existent paths are ignored. When an agent is started sometime after the VM has started then paths that do not represent a JAR file are ignored. This attribute is optional.
  • ACan-Redefine-Classes : Boolean (true or false, case irrelevant). Is the ability to redefine classes needed by this agent. Values other than true are considered false. This attribute is optional, the default is false.
  • Can-Retransform-Classes : Boolean (true or false, case irrelevant). Is the ability to retransform classes needed by this agent. Values other than true are considered false. This attribute is optional, the default is false.
  • Can-Set-Native-Method-Prefix  : Boolean (true or false, case irrelevant). Is the ability to set native method prefix needed by this agent. Values other than true are considered false. This attribute is optional, the default is false.

뭐 이런 정보라고 자바독에서 말씀하신다. ^^;

본인은 간단(?)한게 Premain-Class, Can-Redefine-Classes 두가지를 사용해보았다.

Manifest-Version: 1.0
Premain-Class: kr.kangwoo.fly.MyAgent
Can-Redefine-Classes: true

Premain-Class는 말그대로 메인(premain) 메소드가 있는 클래스를 지정해주는것이고, Can-Redefine-Classes는 클래스를 재정의(redefine)해주기 위한것이다. 
 Instrumentation.redefineClasses(java.lang.instrument.ClassDefinition[]) 메소드를 사용하기위해서는 Can-Redefine-Classes: true 로 꼭!꼭! 설정해줘야한다.(이 옵션을 몰라 헤맨 아픈기억이 ㅠㅠ, "redefineClasses is not supported in this environment" 에러가 보고싶다면 false로 해주자 ^^;)
 Instrumentation.addTransformer(java.lang.instrument.ClassFileTransformer) 메소드를 이용해서, Custom Class Loader를 따로 만들필요없이, Byte Code Injection을 할 수 있다.(보통 어플리케이션 모니터링 프로그램이 이걸이용한다.)
 하지만, 노가다(?)는 여전한 사실... 다행이, 아래와 같은 라이브러리가 있긴 하지만, 힘들긴 마찬가지 ^^;
cglib(http://cglib.sourceforge.net/)
ASM(http://forge.objectweb.org/projects/asm)
BCEL(http://jakarta.apache.org/bcel)
Javassist(http://www.jboss.org/javassist/)
...

 흠흠, 이 Byte Code Injection을 보면 AOP(Aspect Oriented Programmin)가 떠오르는건 본인이 무식해서 그런것일까? ^^; 일반적으로 AOP는 Java Proxy(java.lang.reflect)를 이용해서 사용한다. 이 Java Proxy는 타겟(target)이 인터페이스일때만 사용 가능하다는 단점이 있다. 그래서 인터페이스를 선언(?)하도록 강요하는 곳도 있고, cglib를 이용해서 인터페이스 없이, 프록싱을 가능하게 하기도 한다.

package kr.kangwoo.fly;

import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.IllegalClassFormatException;
import java.lang.instrument.Instrumentation;
import java.security.ProtectionDomain;

public class MyAgent implements ClassFileTransformer {

	public static void premain(String agentArgument, Instrumentation instrumentation) {
		System.out.println("premain");
		instrumentation.addTransformer(new MyAgent());
	}

	public byte[] transform(ClassLoader loader, String className,
			Class<?> classBeingRedefined, ProtectionDomain protectionDomain,
			byte[] classfileBuffer) throws IllegalClassFormatException {
		System.out.println("transform : " + className);
		return classfileBuffer;
	}

}

java.lang.Class.class
java.lang.reflect.Method.class
java.lang.reflect.Constructor.class
java.lang.reflect.Field.class
java.io.ObjectStreamClass.class
java.net.URLClassLoader.class
java.lang.reflect.Proxy.class
java.lang.ClassLoader.class