2021 06 06
2021-06-06¶
바이트코드¶
- Maven을 사용하여 빌드하기 - 참고 1: https://steven-life-1991.tistory.com/90 - 참고 2: https://ojava.tistory.com/147 - Maven 다운받아서 환경 변수로 bin 추가 - pom.xml - import되는 jar 파일과 버전 정보, 패키징 되는 기준 값 - 기본 구문과 추가 설정으로 나뉨 - Artifact: 프로젝트 생성시 생기는 필수 정보, 배포 방식/정보 포함 - properties: 자주 사용되는 정보 변수처럼 만들어 사용 - dependencies: 의존성 정의. 라이브러리 불러오기
- 바이트 코드 조작
- 기존의 자바 소스 코드를 컴파일해서 생기는 클래스 파일
- 이 클래스 파일을 조작할 수 있음
- 어차피 jvm은 클래스파일을 로드해 한줄씩 읽으며 실행하기에 클래스 파일이 조작되면 코드와 다른 결과가 기대됨
- 다음과 같이 ByteBuddy 라이브러리 사용하여 코드를 작성할 수 있다
- Moja 클래스의 pullout 메서드가 "ByteRabbit!"을 반환하도록 하고, 이를 File("C:~~") 위치에 저장하자
public static void main(String[] args) throws IOException { new ByteBuddy().redefine(Moja.class) .method(named("pullout")).intercept(FixedValue.value("ByteRabbit!")) .make().saveIn(new File("C:\\Users\\joel6\\Desktop\\the-java\\bytecode\\target\\classes")); System.out.println(new Moja().pullout()); }- new Moja().pullout(); - 처음 실행하면, 클래스 파일이 이렇게 작성됨 ```java public class Moja { public Moja() { } public String pullout() { return ""; } } ``` - Moja 클래스가 로딩되는 시점이 redifine(Moja.class)인데, 이때 생성된 클래스 파일을 로드해서 사용 - new Moja().pullout() 에서 사용한 Moja 클래스는 이미 위에서 로딩된 클래스 - 한 번 실행을 하고 나면, 바이트코드가 조작되어 클래스 파일이 다음과 같이 수정됨 ```java public class Moja { public Moja() { } public String pullout() { return "ByteRabbit!"; } } ``` - 이제 Main 실행하면 ByteRabbit! 출력 - 이게 결국 자바 코드상의 바이트 코드 조작이기 때문에, 한 번은 자바 코드를 실행해야 클래스 파일에 내용이 반영이 됨
- javaagent
- 참고 1: https://catsbi.oopy.io/6136946a-9139-4541-b2af-2af93bb634a5
- 정의
- JVM에서 동작하는 Java 어플리케이션으로, JVM의 다양한 이벤트를 전달 받거나, 정보 질의, 바이트코드 제어 등을 특정 API를 통해 수행
- 특징
- Agent는 지정된 JVM의 실행 가능한 최초 진입점인 main 메서드 가로챌 수 있음
- 지정된 JVM에서 실행
- 지정된 JVM의 동일한 System Class Loader 내에서 로드
- 지정된 JVM의 Security policy 및 Context 영향 받음
- 실행시간에 동적으로 bytecode 조작 가능
- 사용 메서드
- Agent의 단일 진입점 premain 메서드
- Instumentation 인터페이스로 바이트 코드를 포함한 추가적인 정보 수집 도구 활용 가능
public static void premain(String agentArgs, Instrumentation inst); public static void premain(String agentArgs)- 사용하기 - 클래스로더가 "클래스를 읽어올 때", javaagent를 거쳐 "변경된 바이트코드를 읽어들여 사용" - 그러다보니까 위에서 살펴볼 땐 한 번 실행해서 java 소스 코드로 바이트코드 변경했자나? - 여기서는 javaagent를 통해 애초 런타임 와중에 클래스 로더에서 읽어올 때 코드를 조작하자나? - 한 번에 바로 조작이 가능하지 - 실습 - 다음과 같이 premain 메서드를 작성하자
public class MagicianAgent { public static void premain(String agentArgs, Instrumentation inst) { new AgentBuilder.Default() .type(ElementMatchers.any()) .transform(new AgentBuilder.Transformer() { public DynamicType.Builder<?> transform(DynamicType.Builder<?> builder, TypeDescription typeDescription, ClassLoader classLoader, JavaModule javaModule) { return builder.method(named("pullout")).intercept(FixedValue.value("RabbitRabbit!")); } }).installOn(inst); } }- 이렇게 작성한 코드를 실행 가능한 jar 파일로 만들자 - 아래 참조 - 이제 javaagent를 통해 JVM Options를 적용하자! - *javaagent:C:\Users\joel6\Desktop\the-java\Magician\target\Magician-1.0-SNAPSHOT.jar* - 요런 갬성으로다가 java 클래스 파일들을 실행할 때 VM options를 줄 수 있나보다! ``` $ java -server -Xms8g -Xmx8g -XX:MaxMetaspaceSize=256m -XX:+CMSClassUnloadingEnabled -Dspring.profiles.active=prod -jar app.jar $ java -jar -Dspring.profiles.active=prod [jar파일명] ``` - 이 친구들은 컴파일된 클래스 파일을 바꾸는 게 아님 - 클래스로더가 클래스를 "로딩"할 때 javaagent의 설정을 읽어들여 바이트코드를 변형하여 로드함
- 실행 가능한 jar 파일 만들기
- 참고 1: https://boxfoxs.tistory.com/332
- pom.xml에서 premain-class에 대한 정보를 적어주자
- addClasspath 설정으로 class-path 요소들을 MANIFEST.MF 파일에 추가하자
<plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-jar-plugin</artifactId> <version>3.1.2</version> <configuration> <archive> <index>true</index> <manifest> <addClasspath>true</addClasspath> </manifest> <manifestEntries> <mode>development</mode> <url>${project.url}</url> <key>value</key> <Premain-Class>MagicianAgent</Premain-Class> <Can-Redefine-Classes>true</Can-Redefine-Classes> <Can-Retransform-Classes>true</Can-Retransform-Classes> </manifestEntries> </archive> </configuration> </plugin>- mvn clean package 를 통해 빌드를 하면 "Magician-1.0-SNAPSHOT.jar"가 생성된 것을 볼 수 있음 - Magician > target > Magician-1.0-SNAPSHOT.jar > META-INF > MANIFEST.MF 로 해당 jar 파일의 정보를 보자
- 바이트 코드 조작이 쓰이는 곳 - 프로그램 분석 - 디버깅 - 코드 복잡도 - 클래스 파일 생성 - 프록시 - 특정 API 호출 접근 제한 - JVM 기반 언어의 컴파일러 - 기타 소스코드 변경 - 프로파일러 - 최적화 - 로깅 - 스프링에서는 "Component Scan!"