Brief introduction:With this in mind, how does Arthas manage to dynamically monitor our code while the program is running? With such a problem, let’s look at the implementation principle of Java Agent technology.

background

The project uses com.github. DreamRoute Excel-Helper to assist in parsing the Excel file. The code for the error is as follows (not source code)

     try {            excelDTOS = ExcelHelper.importFromFile(ExcelType.XLSX, file, ExcelDTO.class);        } catch (Exception e) {            log.error("ExcelHelper importFromFile exception msg {}", e.getMessage());        }

The reason for this is that the testetMessage () method is used to print the exception. And the local duplication didn’t come back. So we can only consider using Arthas to assist in troubleshooting this problem.

The screening process

1. Install Arthas on the online server. https://arthas.aliyun.com/doc/install-detail.html

2. Use watch command to monitor the specified method and print out the abnormal stack information. The command is as follows:

watch com.github.dreamroute.excel.helper.ExcelHelper importFromFile '{params,throwExp}' -e -x 3

Call the method again and catch the following exception stack information:

The exception has been caught and the stack information is printed out.

3. Locate the specific code according to the corresponding stack information, as follows:

The code is very simple, and it is clear from the code that if the specified headerInfo is not retrieved from the HeaderInfoMap, the exception will be thrown. There are only two cases where it is not found:

  • Incorrect information saved in HeaderInfoMap.
  • ColumnIndex in the cell is out of the normal range, resulting in no corresponding headerInfo.

In the second case, I first checked whether there was any problem with the uploaded Excel file. I tested the Excel file locally and found that there was no problem. Local testing was also successful, so judging by this, the second scenario is unlikely.

So the main thing to check is if the first case happens, and you can look at the first line of the method again

MapheaderInfoMap = processHeaderInfo(rows,cls);

You can see that the headerInfoMap is obtained from ProcesSheaderInfo. Find the code for processHeaderInfo, as shown below.

public static MapproceeHeaderInfo(Iteratorrows, Class cls) {
    if (rows.hasNext()) {
        Row header = rows.next();
        return CacheFactory.findHeaderInfo(cls, header);
    }
    return new HashMap<>(0);
}
public static MapfindHeaderInfo(Class cls, Row header) {
    MapheaderInfo = HEADER_INFO.get(cls);
    if (MapUtils.isEmpty(headerInfo)) {
        headerInfo = ClassAssistant.getHeaderInfo(cls, header);
        HEADER_INFO.put(cls, headerInfo);
    }
    return headerInfo;
}
public static MapgetHeaderInfo(Class cls, Row header) {
    IteratorcellIterator = header.cellIterator();
    Listfields = ClassAssistant.getAllFields(cls);
    MapheaderInfo = new HashMap<>(fields.size());
    while (cellIterator.hasNext()) {
        org.apache.poi.ss.usermodel.Cell cell = cellIterator.next();
        String headerName = cell.getStringCellValue();
        for (Field field : fields) {
            Column col = field.getAnnotation(Column.class);
            String name = col.name();
            if (Objects.equals(headerName, name)) {
                HeaderInfo hi = new HeaderInfo(col.cellType(), field);
                headerInfo.put(cell.getColumnIndex(), hi);
                break;
            }
        }
    }

    return headerInfo;
}

The cache is generated using the findHeaderInfo method of the CacheFactory class. In the findHeaderInfo method, the cache is cached using a variable named HEADER\_INFO that is modified with static final. When called, first look in HEADER\_INFO, if there is a direct return, not to create (that means the same Excel file, only initialized once headerInfo). Create the steps in ClassAssistant. GetHeaderInfo () method.

A simple look at the HeaderInfo generation process, according to the Excel file’s first line of each Cell value compared with the annotation of the custom entity class, if the name is the same, it is saved as a key value pair (HeaderInfo’s data structure is HashMap).

4. At this point, you need to make sure that the HEADER\_INFO saved in the ExcelDTO.class related headerInfo is what. Use the ognl command or the getstatic command to see this. The ognl command is used here.

ognl '#value=new com.tom.dto.ExcelDTO(),#[email protected]@HEADER_INFO,#valueMap.get(#value .getClass()).entrySet().iterator.{#this.value.name}'

The results are as follows: Why are only four key-value pairs generated when this Excel file normally has six columns of information? If an error is saved in HEADER\_INFO, from the above logic, subsequent uploads of the correct Excel file will be thrown in error during parsing.

5. I asked the colleague who found this problem at that time, and learned that there was something wrong with the Excel file he uploaded the first time. Later, he tried to correct it, but there was something wrong when he uploaded it again. So that’s the problem.

Arthas principle exploration

With this in mind, how does Arthas manage to dynamically monitor our code while the program is running? With such a problem, let’s look at the implementation principle of Java Agent technology.

Java Agent technology

An Agent is a specific program that runs in the target JVM and is responsible for fetching data from the target JVM and passing the data to the external process. The time to load an Agent can be when the target JVM is started or when the target JVM is running, and it is dynamic to load the Agent when the target JVM is running.

Basic concept

  • JVMTI (JVM Tool Interface) : JVMTI (JVM Tool Interface) is a set of interfaces exposed by the JVM that can be extended by the user. JVMTI is event-driven. Every time the JVM executes a certain logic, it calls some callback Interface (if any) of the event, which can be extended by the developer.
  • JVMTIAgent (JVM Tool Interface) : This is a dynamic library that uses some of the interfaces exposed by JVMTI to help the JVM Attach the Agent to the target JVM when the program starts or runs.
  • JPLISAgent (Java Programming Language Instrumentation Services Agent) : It initializes all agents written through the Java Instrumentation API and is responsible for exposing the Java Instrumentation API through the JVMTI implementation.
  • VirtualMachine: The Attach action and the Detach action are provided, allowing us to connect remotely to the JVM via the Attach method and then register an agent agent with the JVM via the loadAgent method. In the agent’s agent, we get a Instrumentation instance, which can change the bytecode of a class before it is loaded, or reload it after the class is loaded.
  • Instrumentation: The Instrumentation can change the class bytecode premain before the class is loaded, and it can reload the class after the class is loaded.

Implementation process

Write a Demo

With Javassist, you change the code that specifies the method at run time, adding custom logic before and after the method.

1. Define Agent class. Java currently provides two ways to inject code into the JVM, and here we choose to use the agentMain method for this Demo.

Premain: Injects agents into the specified JVM via the JavaAgent command at startup. AgentMain: The specified agent is activated by the Attach tool at runtime.

/** * AgentMain * * @author tomxin */ public class AgentMain { public static void agentmain(String agentArgs, Instrumentation instrumentation) throws UnmodifiableClassException, ClassNotFoundException { instrumentation.addTransformer(new InterceptorTransformer(agentArgs), true); Class clazz = Class.forName(agentArgs.split(",")[1]); instrumentation.retransformClasses(clazz); } } /** * InterceptorTransformer * * @author tomxin */ public class InterceptorTransformer implements ClassFileTransformer { private String agentArgs; public InterceptorTransformer(String agentArgs) { this.agentArgs = agentArgs; } @Override public byte[] transform(ClassLoader loader, String className, Class classBeingRedefined, ProtectionDomain ProtectionDomain, byte [] classfileBuffer) throws IllegalClassFormatException {/ / javassist package name is separated with points, If (ClassName! = null && className.indexOf("/") ! = -1) { className = className.replaceAll("/", "."); } try {// ctClass CC = ClassPool.getDefault().get(ClassName); CtMethod m = cc.getDeclaredMethod(agentArgs.split(",")[2]); / / insert code before method performs m.i nsertBefore (" {System. Out. Println (\ "= = = = = = = = = begin to execute = = = = = = = = = \"); } "); M.i nsertAfter (" {System. Out. Println (\ "= = = = = = = = = end = = = = = = = = = \"); } "); return cc.toBytecode(); } catch (Exception e) { } return null; }}

2. Use Maven to configure the MANIFEST.MF file, which specifies the main method of the JAR package.

<build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-jar-plugin</artifactId> </version> 2.3.1</version> <configuration> <archive> <manifest> <addClasspath>true</addClasspath> </manifest> <manifestEntries> <Agent-Class>com.tom.mdc.AgentMain</Agent-Class> <Can-Redefine-Classes>true</Can-Redefine-Classes> <Can-Retransform-Classes>true</Can-Retransform-Classes> </manifestEntries> </archive> </configuration> </plugin> </plugins> </build>

3. Define the Attach method, using VirtualMachine. ATTACH (#{pid}) to specify the class to be proxies.

import com.sun.tools.attach.VirtualMachine; import java.io.IOException; /** * AttachMain * * @author tomxin */ public class AttachMain { public static void main(String[] args) { VirtualMachine  virtualMachine = null; try { virtualMachine = VirtualMachine.attach(args[0]); // Add the packaged JAR to the specified JVM process. VirtualMachine. LoadAgent (" target/agent - demo - 1.0 - the SNAPSHOT. Jar ", String. Join (", ", args)); } catch (Exception e) { if (virtualMachine ! = null) { try { virtualMachine.detach(); } catch (IOException ex) { ex.printStackTrace(); } } } } }

4. Define the test method

package com.tom.mdc; import java.lang.management.ManagementFactory; import java.util.Random; import java.util.concurrent.TimeUnit; /** * PrintParamTarget * * @author toxmxin */ public class PrintParamTarget { public static void main(String[] args) { / / print the current process ID System. Out. Println (ManagementFactory. GetRuntimeMXBean (). The getName ()); Random random = new Random(); while (true) { int sleepTime = 5 + random.nextInt(5); running(sleepTime); } } private static void running(int sleepTime) { try { TimeUnit.SECONDS.sleep(sleepTime); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("running sleep time " + sleepTime); }}

Copyright Notice:The content of this article is contributed by Aliyun real-name registered users, and the copyright belongs to the original author. Aliyun developer community does not own the copyright and does not bear the corresponding legal liability. For specific rules, please refer to User Service Agreement of Alibaba Cloud Developer Community and Guidance on Intellectual Property Protection of Alibaba Cloud Developer Community. If you find any suspected plagiarism in the community, fill in the infringement complaint form to report, once verified, the community will immediately delete the suspected infringing content.