"In a production environment, 'observability' shouldn't come at the cost of stability. The questions that often linger : 'How much overhead does this add?' or 'How does it handle my complex multi-threaded transactions?' The New Relic Java Agent is designed to be a silent, pass-through observer, utilizing the JVM’s own instrumentation engine to watch your code with almost zero footprint. From the 'Circuit Breaker' that protects your memory to the automatic context propagation that follows your data across threads, let’s explore the engineering marvel that keeps your apps monitorable without breaking them."
The New Relic Java Agent
When you download the latest version of the Java agent from NR (zip file), you extract the contents of the zip file to the file system where the Java app is installed. The extracted directory consists of several files, but the most important ones are:
- Newrelic.yml (agent configuration, at min. You need to add the license key)
- Newrelic.jar (instrumentation)
Configuration Precedence Hierarchy
To ensure the desired parameters are applied, observe the following order of priority:
- Server-Side Configuration: When server-side configuration is enabled, configurations managed via the New Relic UI supersede all other local settings.
- Environment Variables: Primarily designed for containerized or cloud-native environments like Heroku, environment variables take precedence over both the local configuration file and system properties.
- System Property Overrides: Individual settings within the newrelic.yml file may be overridden at the command line using Java system properties
- Local Configuration File (newrelic.yml): This serves as the foundational settings layer and is only applied if no higher-level overrides are present.
Static & Dynamic Agents
Before we foray into the working of the New Relic Java agent. Here’s the two ways in which it can be invoked.
Static, via -javaagent (most preferred method)
This is the standard, "official" way to run the New Relic Java agent. You provide the path to the agent JAR as a JVM argument when you start the process. It is initiated at JVM startup, before the main method of your application even runs.
It uses the JVM's premain entry point.The premain method is inside that BootstrapAgent class.The Workflow: The JVM starts up, calls the Agent's premain. The Agent modifies your app's bytecode. The JVM finally calls your Application's main. (Discussed in more detail in the next section)
Requires a restart of the Java process to apply or update.
Dynamic, via Attach API ( infrequently used)
A "dynamic" agent is one that is attached to a JVM that is already running.
It is initiated after the JVM has started. You might have a process that has been running for days, and you decide to "inject" the agent into it. It uses the agentmain entry point. It specifically relies on the JDK Attach API (or tools like jattach).
Under the hood : -javaagent
Discussing more on how the static agent functions
The -javaagent (this tells the JVM to include the the java agent JAR file) option in the JVM is used to specify a Java agent, a component that can inspect/modify the bytecode at runtime.
To use it, the “-javaagent’ option is added to the JVM startup command line as follows:
java -javaagent : path/to/agent.jar = <options> -jar newrelic.jar
‘path/to/agent.jar’ : specifies the absolute full path to the JAR file containing the agent’s implementation.
‘<options>’ : an optional string that can be used to pass configuration options to the agent.
When you pass the -javaagent flag, the JVM enters a specific "startup phase" before your application code even exists in memory.
The very first thing the JVM does is locate and process the Agent's Manifest file (MANIFEST.MF) to validate the agent's "credentials."
The Step-by-Step Startup Sequence
- JAR Validation: The JVM locates the JAR file specified in your command (e.g.,
newrelic.jar) and opens its internalMETA-INF/MANIFEST.MFfile. - Entry Point Identification: It looks specifically for the
Premain-Classattribute. As you saw in your image, this points the JVM tocom.newrelic.bootstrap.BootstrapAgent. - Agent Loading: The JVM loads the Agent class into a special "System ClassLoader."
- The
premainCall: The JVM invokes thepremainmethod inside that class.
The JVM does this early because it needs to give the agent an Instrumentation object. This object acts like a "hook" into the JVM's class-loading engine.
By doing this first, the JVM ensures that when your application later tries to load a class (like a Database Driver or a Spring Controller), the agent is already standing by to "rewrite" that class's bytecode before it ever starts running.
The Java Virtual Machine (JVM) executes this step early to provide the agent with an Instrumentation object. This object serves as a mechanism to interface directly with the JVM's class-loading apparatus.
This initial process ensures that the agent is prepared to modify the bytecode of classes—such as a Database Driver or a Spring Controller—prior to their loading and execution by the application.
The most critical thing that happens inside the premain method is that the agent (New Relic) calls instrumentation.addTransformer(myTransformer).This tells the JVM: "From now on, before you finalize any class into memory, you must send it through this transformer first."
Class Transformations - these are performed to inject additional behaviour to the bytecode of classes without changing the original source code. This makes the app code monitorable or rather observable. For instance, java.lang.instrument can instrument classes by altering bytecode at load-time.
To summarise, the following tasks are performed at startup:
- Logging is set-up.
- Class transformers are created & registered.
- Several services are initialized :
Config Service (Reads the configuration from the .yml file)
RPM Connection (Establishes & maintains connection to NewRelic)
Harvest Service (Collects metrics & reports to NewRelic)
Transaction (Collects transactions as the application runs)
The New Relic Java agent is designed to be a "pass-through" observer with a very small footprint. It does not store data locally on your disk before sending it to NRDB.
Instead, it uses an in-memory "Harvest Cycle" to batch data and send it out periodically.
The agent uses your application's RAM (Heap memory) as a temporary staging area.
- Metric Collection: As your app runs, the agent collects metrics (like response times and error counts) in real-time.
- Aggregation: It holds these metrics in a memory buffer for a 60-second "Harvest Cycle".
- Transmission: Every minute, the agent packages this buffered data into a JSON message and sends it over HTTPS to New Relic's collectors.
- Purge: Once the transfer is confirmed as successful, the agent clears that specific "bucket" of memory to make room for the next minute's data.
The same holds good if logs-in-context are also enabled.
Custom Instrumentation
While the New Relic Java agent automatically instruments hundreds of popular frameworks, custom instrumentation is required to gain visibility into proprietary business logic or unsupported third-party libraries. Customers opt for this when they need to track specific "black box" methods that are critical to their operations but aren't detected by default. By defining these custom markers, users can transform generic background tasks into named transactions, ensuring that every high-value code path is measured and alerted upon. Essentially, it bridges the gap between standard framework monitoring and the unique, high-stakes code that is critical in running the customer's business.
The agent acquires instrumentation data primarily from two distinct sources:
- Within
newrelic.jar(the default source) - The Extensions Directory (for custom instrumentation)
Option available to Custom Instrument :
CIE (Custom Instrumentation Editor)
XML Instrumentation
YML Instrumentation
JAR files (weaver and many more)
@trace
The @Trace annotation is the most direct way for a developer to tell the New Relic agent, "I want to see the performance of this specific method in my dashboard".
How @Trace Functions Under the Hood
- Marker Identification: During the "Static Loading" phase (the
pre-mainprocess), the New Relic agent scans your application’s compiled classes specifically looking for the@Traceannotation. - Bytecode Injection: When the JVM loads a class containing this annotation, the agent's class transformer injects "start" and "stop" timers directly into that method's bytecode.
- Transaction Linking: If the annotated method is called while a web request is active, the agent automatically links this method's timing data to that specific transaction.
- Segment Creation: In the New Relic UI, this method will now appear as a distinct "Segment" within the transaction breakdown, allowing you to see exactly how many milliseconds that specific block of code consumed.
- Dispatcher Trigger: If
@Trace(dispatcher = true)is used, it tells the agent to start a brand-new transaction if one isn't already running—perfect for background jobs or messaging consumers.
Why This Is Chosen
While other methods like XML or the Custom Instrumentation Editor (CIE) don't require code changes, developers prefer @Trace because it keeps the monitoring logic right next to the source code, making it easier to maintain during refactoring.
As of today, NewRelic’s JAVA APM Agent can be installed on :
- On a Host
- Docker
- Kubernetes
- Gradle
- Maven
- Lambda
- Using Ansible
Handling Asynchronous Behaviour
The New Relic Java agent handles multi-threaded behavior by ensuring that the "context" of a transaction is preserved and passed accurately as work moves across different threads.
In a standard single-threaded request, the agent uses Thread Local Storage (TLS) to keep track of the transaction. However, since Java often splits work across thread pools or asynchronous frameworks, the agent uses three main strategies to maintain visibility:
1. Automatic Context Propagation
For many popular frameworks (like Spring WebFlux, Akka HTTP, and Play), the agent automatically "hooks" into the underlying thread management. When a parent thread hands off a task to a child thread or an executor service, the agent interceptor captures the transaction state and "injects" it into the new thread, ensuring the sub-tasks are still linked to the original web request.
2. The "Token" System (Asynchronous API)
For custom multi-threading or unsupported frameworks, the agent uses a Token-based API to link work together:
getToken(): You call this on the original thread to create a physical "link" to the current transaction.token.link(): You call this on the new worker thread. This tells the agent, "Whatever happens on this thread now belongs to that original transaction".token.expire(): This signals the end of the async work so the transaction can finally close and be reported.
3. Thread Profiling
To monitor the health of all threads regardless of specific transactions, the agent includes a Thread Profiler.
- It captures stack traces of all active threads every 100ms for a specified duration.
- This allows it to identify "hot spots" where threads are blocked or competing for resources, even if they aren't part of a tracked transaction.
Circuit Breaker
Because the data is stored in memory, New Relic has built-in safeguards(circuit breaker) to ensure the agent doesn't crash your application if there is a massive spike in traffic or errors:
- Sampling: The agent has a maximum number of events (like transaction traces or logs)it will store per minute. If you exceed these limits, , the agent will "sample" the data—keeping a representative slice and discarding the rest to protect your memory.
- The Circuit Breaker: If the agent detects that the JVM is nearly out of heap space, it will "trigger" a circuit breaker. It will stop collecting and storing new data entirely until memory levels return to a safe threshold, preventing the agent from causing an
OutOfMemoryError
This also happens when you tend to over instrument your app code and too much data is being generated by the agent.
The New Relic Java Agent seamlessly integrates JVM-native instrumentation for deep, non-intrusive visibility into complex, multi-threaded environments. Deploy this powerful agent across your infrastructure to gain comprehensive observability. Transform telemetry into actionable insights, ensuring system stability and driving operational excellence.
Étapes suivantes
Getting started with JAVA APM monitoring using the latest updates from New Relic is straightforward:
Ensure you have a New Relic account and appropriate permissions to access monitoring features.Sign up for a free New Relic account. Your account includes 100 GB/month of free data ingestion, one free full-platform user, and unlimited basic users.
Learn more about agent configuration, requirements & compatibility and latest release updates.
Les opinions exprimées sur ce blog sont celles de l'auteur et ne reflètent pas nécessairement celles de New Relic. Toutes les solutions proposées par l'auteur sont spécifiques à l'environnement et ne font pas partie des solutions commerciales ou du support proposés par New Relic. Veuillez nous rejoindre exclusivement sur l'Explorers Hub (discuss.newrelic.com) pour toute question et assistance concernant cet article de blog. Ce blog peut contenir des liens vers du contenu de sites tiers. En fournissant de tels liens, New Relic n'adopte, ne garantit, n'approuve ou n'approuve pas les informations, vues ou produits disponibles sur ces sites.