Hello everyone, I am a small dish.
A man who hopes to be about architecture ! If you also want to be the person I want to be, otherwise click on your attention and be a companion, so that Xiaocai is no longer alone!
This article mainly introduces the
Java Advanced Usage
If necessary, you can refer to
If it helps, don’t forget to 161acccfd81964 ❥
The WeChat public account has been opened, said , students who have not followed please remember to pay attention!
Xiao Wang is a girl who has just arrived, ah, yeah, she is a program lady who has just arrived, and she often feels dejected~ I am very puzzled. Finally, one day I am afraid that Xiao Wang will not be able to leave her job. Wouldn't it increase me? Workload (one of the few department-1)? Since then, I took the initiative to talk to Xiao Wang and found the problem. It turned out that Xiao Wang had insufficient programming experience and did not know how to print logs cleverly. Then the causal relationship was summarized:
experience led to frequent coding errors. Errors are difficult to troubleshoot because the logs are not printed, and the troubles in troubleshooting lead to depression in development. If you find the cause of the problem, you can prescribe the right medicine~
In fact, I believe that many small partners have encountered the above problems. Errors that did not appear during the development process appeared frequently after they went online. Then you can only continue to add log printing and then package and upload for problem tracking. Most of the day All wasted on uploading packages. So can you directly track the bug, and then see where the problem went wrong? This demand is no less than to replace the tires of a running car. It is incredible but helpless~ In fact, a small partner with development experience has come up with a middleware, that is
Arthas
! But this article is not about how to use Archas
, but can we realize this kind of dynamic debugging skills ourselves? Then enter our overall today --- Java Agent technology
Java Instrument
This thing is not a new feature of Java. It was born as early as JDK 1.5 and is located in java.lang.instrument.Instrumentation
api
of the calss file of a certain class at runtime.
The implementation of this class is actually a Java Agent
technology. Here we can take a look at what Java Agent is.
One, Java Agent
The word proxy is not a default for our developers. The AOP aspect-oriented programming we often use is the proxy method. It can dynamically cut into a certain surface for code enhancement. This way of not having to replenish the wheels has greatly increased our development efficiency, so here is a keyword dynamic. So how is the Java Agent implemented? It can be said that JVMTI (JVM Tool Interface) , this is the Native programming interface provided by the Java virtual machine, through which we can obtain a lot of information about the runtime JVM, and Agent is a specific running on the target JVM Program, it can obtain data from the target JVM, and then pass the data to the external process, and then the external process can dynamically Enhance based on the obtained data.
So when can the Java Agent be loaded?
- When the target JVM
starts
- Target JVM
runtime
Then we are concerned about the runtime, so that we can meet our needs for dynamic loading.
And the Java Agent looks so tall, how do we write it? Of course, before JDK 1.5, it was difficult to implement. We need to write Native code to implement it. After JDK 1.5, we can use the Java Instrument
mentioned above to implement it!
First, let's first understand the Instrumentation interface, which has several methods:
addTransformer(ClassFileTransformer transformer, boolean canRetransform)
Add a transformer Transformer , then all target class loading will be intercepted by Transformer, you can customize the implementation of ClassFileTransformer interface, the only way to rewrite the interface transform()
method, the return value is the converted class bytecode file
retransformClasses(Class<?>... classes)
Re-trigger class loading for classes that have already been loaded by the JVM, and use the above-defined converter for processing. This method can modify the method body, constant pool and attribute values, but cannot add, delete, or rename attributes or methods, nor can it modify the signature of the method
redefineClasses(ClassDefinition... definitions)
This method is used to replace the definition of the class without referencing the bytes of the existing class file.
getObjectSize(Object objectToSize)
Get the size of an object
appendToBootstrapClassLoaderSearch(JarFile jarfile)
Add a jar file to the classPath of bootstrap classload
getAllLoadedClasses()
Get all class objects currently loaded by the JVM
Supplementary description of redefineClasses and retransformClasses
- The difference between the two:
redefineClasses is to provide bytecode files to replace the existing class files
retransformClasses is to modify the existing bytecode file before replacing it
- Timing of taking effect after replacement
If a modified method already exists in the stack frame, the method in the stack frame will continue to run with the old bytecode, and the new bytecode will run in the new stack frame
- be careful
Both methods can only change the method body, constant pool and attribute value of the class, but cannot add, delete, or rename attributes or methods, nor can the signature of the method be modified
Second, realize the Agent
1. Writing method
As mentioned above, there are two places where Java Agent can be loaded. They are target JVM loading at startup and
target JVM runtime loading. These two different loading modes use different entry functions:
1. JVM starts
The entry function is as follows:
// 函数1
public static void premain(String agentArgs, Instrumentation inst);
// 函数2
public static void premain(String agentArgs);
The JVM first looks for function 1, if it does not find function 1, it will look for function 2
2. JVM is running
The entry function is as follows:
// 函数1
public static void agentmain(String agentArgs, Instrumentation inst);
// 函数2
public static void agentmain(String agentArgs);
Consistent with the above, the JVM first looks for function 1, and if it does not find function 1, it will look for function 2
The first parameter agentArgs
of these two methods is the program parameter passed along with "-javaagent". If this string represents multiple parameters, you need to parse this parameter yourself. inst
is an object of type Instrumentation, which is the JVM itself Incoming, we can use this parameter to perform parameter enhancement operations.
2. Method of declaration
After defining these two sets of methods, you need to declare them manually to make them effective. There are two ways to declare:
1. uses the MANIFEST.MF file
We need to create the resources/META-INF.MANIFEST.MF
file, and package the file together when the jar package is packaged. The content of the file is as follows:
Manifest-Version: 1.0
Can-Redefine-Classes: true # true表示能重定义此代理所需的类,默认值为 false(可选)
Can-Retransform-Classes: true # true 表示能重转换此代理所需的类,默认值为 false (可选)
Premain-Class: cbuc.life.agent.MainAgentDemo #premain方法所在类的位置
Agentmain-Class: cbuc.life.agent.MainAgentDemo #agentmain方法所在类的位置
2. is a maven project, add
3. Specify the agent
To make the target JVM recognize you this Agent, you have to introduce this Agent to the target JVM
1. JVM starts
We directly add the -javaagent parameter to the JVM startup parameters and specify the location of the jar file
# 将该类编译成 class 文件
javac TargetJvm.java
# 指定agent程序并运行该类
java -javaagent:./java-agent.jar TargetJvm
2. JVM is running
To achieve dynamic debugging, we cannot stop the target JVM and restart it. This is not in line with our original intention, so we can use the Attach Api of the JDK to mount the Agent at runtime.
Attach Api is a set of extended APIs provided by SUN, used to attach (attach) to the target JVM to the target program. With it, we can easily monitor a JVM. The code corresponding to Attach Api is located in the com.sun.tools.attach
package, and the functions provided are also very simple:
- List all current JVM instance descriptions
- Attach to one of the JVMs to establish a communication channel
- Let the target JVM load the Agent
There is a class VirtualMachine
under this package, which provides two important methods:
VirtualMachine attach(String var0)
Pass a process number and return the vm object of the target JVM process. This method is a bridge for instruction transfer between JVM processes. The bottom layer is to communicate through sockets.
void loadAgent(String var1)
This method allows us to pass the address of the jar file corresponding to the agent as a parameter to the target JVM, and the target JVM will load the agent after receiving the command
With Attach Api, we can create a java process, use it to attach to the corresponding jvm, and load the agent.
The following is a simple Attach code implementation:
Note: The VirtualMachine class can be found directly in the jdk installed on the mac, but the jdk installed in windows cannot be found. If you encounter this situation, please manually place your jdk installation directory: tools in the lib directory. The jar is added to the Libraries of the current project.
The above code is very simple to implement the Attach method, by searching for all running JVM processes in the current system, and then filtering out the target JVM by comparing the PID, and then allowing the Agent to attach to the target JVM. Of course, it is easy to specify the PID of the target JVM directly in the code. This method is very undesirable in actual production. We can pass in the PID through dynamic parameters~! The execution principle of Attach is not complicated, and the simple process is as follows:
3. Case description
We briefly talked about the implementation process of Java Agent above, then we will also write a simple case to understand the implementation process of Java Agent~
We mentioned above that you can use Java Instrumentation to complete the function of dynamic class modification, and in the Instrumentation interface we can addTransformer()
method, which is implemented by the class ClassFileTransformer interface. There is a unique method transform()
in this interface for implementing class conversion, which is where we can enhance class processing! When the class is loaded, the transform()
method is called to intercept the event of the class loading and return the new bytecode after conversion. The reload event of the class can be triggered redefineClasses()
or retransformClasses()
Actual operation
1) Prepare the target JVM
Here we directly use a SpringBoot project to experiment, so that everyone can enhance and transform~ The project structure is as follows:
target-jvm
├─src
├─main
├─java
└─cbuc
└─life
└─targetjvm
├─controller
| └─TestController.java
└─service
| └─SimpleService.java
└─TargetJvmApplication.java
TestController
and SimpleService
is also very simple, directly paste the code
2) Prepare Agent
1. Writing method
Then we write our Agent
jar package. Because of laziness, I wrote the premain and agentmain methods in the same jar package, and then simulated the scene startup and
runtime~
It's very simple. A class contains all the functions we need~ To prevent the image content from being too crowded, Xiaocai carefully pastes the core code separately:
premain
agentmain
ClassFileTransformer
2) Declare method
Then package the Agent and add the following content to the pom.xml file when packaging
Then run mvn assembly:assembly
3) Start the Agent
When we have prepared two jar packages, we can start testing!
1. Load
nohup java -javaagent:./java-agent-jar-with-dependencies.jar -jar target-jvm.jar &
We add parameters when we start directly, and bring our Agent jar package
The result did not make Xiaocai too awkward, and successfully realized the function we wanted, but this is only loaded at startup, obviously not what we want~ Let’s try how to load it at runtime.
2. Load at runtime
Under normal operation, the method does not make time-consuming statistics, and our demand is here. We want to count the time-consuming method of the method, first obtain the process ID
Then attach the Agent through the Attach method (call the active() method of the controller), and we can view the console in real time
You can already see that the Agent seems to have successfully attached, and then we continue to request the test interface
You can find that the resolve method has been enhanced by us!
Four, off-topic
We have simply implemented the dynamic operation target files above. The beginning of the article explained that is an incredible but helpless need to replace the tires of a running car. However, whether this need can be realized by others is actually possible, and This is the main purpose of Xiaocai. After we understand how to realize the principle of dynamic tire change, when we use its mature middleware, we can handle it more
Words, but when others realize it, we can think silently, and then say
~! Interested students may wish to pull the source code for a walkthrough: Arthas gitee , students who have already used Arthas or BTrace, I believe they will have a better understanding of its working principle after reading it. Those who have not used it will use it next time. Nor will it be so trembling!
Don't talk about it, don't be lazy, and be a X as an architecture with Xiaocai~ Follow me to be a companion, so that Xiaocai is no longer alone. See you below!
If you work harder today, you will be able to say less begging tomorrow!
I am Xiaocai, a man who becomes stronger with you.💋
WeChat public account has been opened, said , students who are not following please remember to pay attention!
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。