DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Please enter at least three characters to search
Refcards Trend Reports
Events Video Library
Refcards
Trend Reports

Events

View Events Video Library

Zones

Culture and Methodologies Agile Career Development Methodologies Team Management
Data Engineering AI/ML Big Data Data Databases IoT
Software Design and Architecture Cloud Architecture Containers Integration Microservices Performance Security
Coding Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Culture and Methodologies
Agile Career Development Methodologies Team Management
Data Engineering
AI/ML Big Data Data Databases IoT
Software Design and Architecture
Cloud Architecture Containers Integration Microservices Performance Security
Coding
Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance
Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks

Last call! Secure your stack and shape the future! Help dev teams across the globe navigate their software supply chain security challenges.

Modernize your data layer. Learn how to design cloud-native database architectures to meet the evolving demands of AI and GenAI workloads.

Releasing software shouldn't be stressful or risky. Learn how to leverage progressive delivery techniques to ensure safer deployments.

Avoid machine learning mistakes and boost model performance! Discover key ML patterns, anti-patterns, data strategies, and more.

Related

  • Type Variance in Java and Kotlin
  • How to Convert XLS to XLSX in Java
  • Recurrent Workflows With Cloud Native Dapr Jobs
  • Java Virtual Threads and Scaling

Trending

  • Concourse CI/CD Pipeline: Webhook Triggers
  • Emerging Data Architectures: The Future of Data Management
  • Medallion Architecture: Efficient Batch and Stream Processing Data Pipelines With Azure Databricks and Delta Lake
  • Understanding and Mitigating IP Spoofing Attacks
  1. DZone
  2. Coding
  3. Java
  4. Java Bytecode: Journey to the Wonderland (Part 3)

Java Bytecode: Journey to the Wonderland (Part 3)

Examine the tools and methods used to change and work with Java bytecode, the Java Virtual Machine's (JVM) intermediate representation of Java code.

By 
A N M Bazlur Rahman user avatar
A N M Bazlur Rahman
DZone Core CORE ·
Mar. 06, 23 · Tutorial
Likes (2)
Comment
Save
Tweet
Share
3.8K Views

Join the DZone community and get the full member experience.

Join For Free

Our previous article unpacked bytecode further and discussed ConstantPool. Today, I'll go through several resources for working with it now.

Java bytecode is the Java Virtual Machine's (JVM) intermediate representation of Java code. While Java bytecode is not meant to be human-readable, it may be edited and manipulated for several reasons. This article examines the tools and methods used to change and work with Java bytecode.

Changing Java bytecode is often done to add new features to a Java program that already exists. This can be done with a bytecode injector, a tool that lets you add bytecode to a Java class file that has already been compiled. Bytecode injectors are often used to log or debug information and to allow updates like A/B testing or feature flags while the program is running.

Javaassist is one of the tools that can be leveraged to inject bytecode. Look at the following class.

Java
 
package ca.bazlur;

public class Greetings {

  public void sayHello(String name) {
    System.out.println("Hello " + name + "!");
  }
}


Let's say we have this class and would like to add a method to it but through bytecode manipulation.

Java
 
package ca.bazlur;

import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import javassist.CannotCompileException;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtMethod;

public class BytecodeInjector {

  public static void main(String[] args) throws IOException {

    try (var resource = BytecodeInjector.class.getResourceAsStream("Greetings.class")) {
      final var classBytes = resource.readAllBytes();

      // Create a ClassPool and import the original class
      ClassPool classPool = ClassPool.getDefault();
      CtClass ctClass = classPool.makeClass(new java.io.ByteArrayInputStream(classBytes));

      // Create a new method and add it to the class
      CtMethod newMethod = CtMethod.make("""
            public void printHelloWorld() {
              System.out.println("Hello, world!");
            }
          """, ctClass);
      ctClass.addMethod(newMethod);

      // Write the modified class back to a byte array
      byte[] modifiedClassBytes = ctClass.toBytecode();

      // Load the modified class bytes into the JVM
      MyClassLoader classLoader = new MyClassLoader();
      Class<?> modifiedClass = classLoader.defineClass("ca.bazlur.Greetings", modifiedClassBytes);

      // Invoke the new method on an instance of the modified class
      Object obj = modifiedClass.newInstance();
      Method method = modifiedClass.getMethod("printHelloWorld");
      method.invoke(obj);
    } catch (CannotCompileException | InvocationTargetException | InstantiationException |
             IllegalAccessException | NoSuchMethodException e) {
      throw new RuntimeException(e);
    }
  }
}


In this code, we had a class called Greetings. We wanted to add a new method. To do that, we had to read the original class into a byte array, import it into a ClassPool, and then modify it by adding a new method. Then, the modified class is written back to a byte array and loaded into the JVM using a custom ClassLoader. Finally, the new method is invoked on an instance of the modified class.

Java
 
package ca.bazlur;

public class MyClassLoader extends ClassLoader {
  public Class<?> defineClass(String name, byte[] bytes) {
    return super.defineClass(name, bytes, 0, bytes.length);
  }
}


If we run the above class, we will see that the functionality has been added to the Greetings class and also executed. It will print:

Hello, world!


There is also a program known as "Byte Buddy," and we can make use of it to do a similar thing.

Let's assume we want to know how much time a method takes to execute. We can make use of bytecode instrumentation. Bytecode instrumentation is another method for modifying Java bytecode. This is done by using a library or tool to change the bytecode of a Java class before the JVM loads it. This might be beneficial for adding performance monitoring or code profiling to an application.

Let's use Byte Buddy to build a simple agent that will instrument every class and calculate the time it takes for each method to execute.

Java
 
package ca.bazlur;

import java.lang.instrument.Instrumentation;
import net.bytebuddy.agent.builder.AgentBuilder;
import net.bytebuddy.asm.Advice;
import net.bytebuddy.matcher.ElementMatchers;

public class MyAgent {

  public static void premain(String agentArgs, Instrumentation inst) {
    new AgentBuilder.Default()
        .type(ElementMatchers.any())
        .transform((builder, typeDescription, classLoader, module) -> builder
            .method(ElementMatchers.any())
            .intercept(Advice.to(TimerAdvice.class)))
        .installOn(inst);
  }
}


The TimerAdvice class is here:

Java
 
package ca.bazlur;

import net.bytebuddy.asm.Advice;

public class TimerAdvice {

  @Advice.OnMethodEnter
  static long invokeBeforeEachMethod(
      @Advice.Origin String method) {
    System.out.println("Entering to invoke : " + method);
    return System.currentTimeMillis();
  }

  @Advice.OnMethodExit
  static void invokeWhileExitingEachMethod(@Advice.Origin String method,
      @Advice.Enter long startTime) {
    System.out.println(
        "Method " + method + " took " + (System.currentTimeMillis() - startTime) + "ms");
  }
}


The full source code is available at the Byte Code tutorial on GitHub. 

Once we've built it and generated a jar, we can use it in the CLI by issuing the following command:

java -javaagent:myagent-1.0-SNAPSHOT.jar MyAwesomeJavaProgram


The MyAwesomeJavaProgram looks like this:

Java
 
public class MyAwesomeJavaProgram {
  public static void main(String[] args) {
    System.out.println(doCalculation());
  }

  public static int doCalculation() {
    int result = 0;
    for (int i = 0; i < 100000000; i++) {
      result += i;
    }
    return result;
  }
}


Once we run it in the CLI, we will get the output as follows:

Entering to invoke : public static void MyAwesomeJavaProgram.main(java.lang.String[])
Entering to invoke : public static int MyAwesomeJavaProgram.doCalculation()
Method public static int MyAwesomeJavaProgram.doCalculation() took 41ms
887459712
Method public static void MyAwesomeJavaProgram.main(java.lang.String[]) took 45ms


Here are some libraries for manipulating Java bytecode:

  1. ASM: A fast, small, and efficient Java bytecode manipulation framework
  2. BCEL: A library for manipulating Java bytecode in the Apache Commons project
  3. Javassist: A bytecode manipulation library for Java
  4. Byte Buddy: A library for generating and modifying Java bytecode
  5. CFR: A bytecode decompiler for Java, written in Java

Changes can be made to Java bytecode for obfuscation and other reasons. "Obfuscation" is the process of making code harder to understand and figure out how it works. This may be beneficial for preventing a program's illegal usage or safeguarding intellectual property.

To make it harder to understand, Java bytecode can be obfuscated by changing the names of classes and methods or adding extra code.

There are various tools for obfuscating Java code available:

  1. ProGuard: This is a free and open-source program for optimizing and obscuring Java code. It can get rid of code that isn't needed, speed up code, and change the names of classes, fields, and functions to make them harder to understand.
  2. DashO: A commercial obfuscation and optimization program that includes control flow obfuscation, string encryption, and watermarking
  3. Zelix KlassMaster: A paid program that provides extensive obfuscation and protection capabilities, such as control flow obfuscation, string encryption, and class and member renaming
  4. Allatori: A commercial product that provides extensive obfuscation and security features such as control flow obfuscation, string encryption, and class and member renaming. 
  5. yGuard: An open-source tool for optimizing and obscuring Java code, it can get rid of code that isn't needed, speed up code, and change the names of classes, fields, and functions to make them harder to understand.

In conclusion, Java bytecode can be updated and controlled for many reasons, like adding new features, instrumenting code for performance monitoring or profiling, or obfuscating code to protect intellectual property.

But even though these methods may work sometimes, they must be used correctly and in accordance with the terms of any license applications.

Java bytecode Java (programming language) Obfuscation (software)

Published at DZone with permission of A N M Bazlur Rahman, DZone MVB. See the original article here.

Opinions expressed by DZone contributors are their own.

Related

  • Type Variance in Java and Kotlin
  • How to Convert XLS to XLSX in Java
  • Recurrent Workflows With Cloud Native Dapr Jobs
  • Java Virtual Threads and Scaling

Partner Resources

×

Comments
Oops! Something Went Wrong

The likes didn't load as expected. Please refresh the page and try again.

ABOUT US

  • About DZone
  • Support and feedback
  • Community research
  • Sitemap

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Core Program
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 3343 Perimeter Hill Drive
  • Suite 100
  • Nashville, TN 37211
  • support@dzone.com

Let's be friends:

Likes
There are no likes...yet! 👀
Be the first to like this post!
It looks like you're not logged in.
Sign in to see who liked this post!