Deep understanding of Java Virtual Machine Optimization

10.1 overview

The compile time of java language is a "uncertain" operation process, because it may refer to the process of a front-end compiler (in fact, it should be called "the front end of the compiler") compiling *. java files into *. class files, or it may refer to the just in time compiler of virtual machine Compiler) the process of converting bytecode into machine code; it may also refer to the process of compiling *. java files into machine code using AOT compiler (head of time compiler). Here are three typical compilers:

  • Front end compiler: Sun's javac, incremental compiler (ECJ) in eclipse JDT.
  • JIT compiler: C1 and C2 compilers of Hotspot VM.
  • AOT compiler: GNU Compiler for the Java (GCJ), Excelsior JET.

Both compile time and compiler in this chapter are limited to the first type of compilation process. In Java, the optimization of real-time compiler at runtime is more important for program running, while the optimization of front-end compiler at compile time is more closely related to program coding.

10.2 Javac compiler

We start to analyze the source code of javac compiler.

10.2.1 source code and debugging of javac

We can create a Java project named "compiler" and copy all the source files in the JDK SRC home / langtools / SRC / share / classes / COM / sum / * directory to the source directory of the project.

The javac compiler process is roughly divided into the following processes:

  • Process of parsing and filling symbol table
  • Annotation processing of plug-in annotation processor
  • Analysis and bytecode generation process

The entry of Javac compilation action is com.sun.tools.javac.main.JavaCompiler class. The code logic of the above three processes is concentrated in the compiler() and compiler2() methods of this class. The following figure shows that the whole compilation process is mainly completed by these eight methods.

10.2.2 table of parsing and filling symbols

The parsing steps are completed by the parseFiles() method, including the lexical analysis and syntax analysis in the classic program compilation principle.

1. Lexical and grammatical analysis

Lexical analysis is to transfer the characters of source code into Token set. A single character is the minimum element of program writing process, while a Token is the minimum element of compilation process. Keywords, variable names, literals and operators can be tags. For example, the code "int a = b + 2" contains six tags. Lexical analysis is implemented by the com.sun.tools.javac.parser.Scanner class.

Syntax analysis is the process of constructing abstract syntax tree according to Token sequence. Abstract syntax tree (AST) is a tree representation used to describe the syntax structure of program code. Each node of the syntax tree represents a syntax structure in program code, such as package, class, modifier, operator, interface, return value and even code annotation. Syntax analysis is implemented by com.sun.tools.javac.parser.Parser class. The abstract syntax tree generated in this stage is represented by com.sun.tools.javac.tree.JCTree. After this step, the compiler will basically not operate on the source file, and subsequent operations are built on the abstract syntax tree.

2. Fill in symbol table

The next step is the process of populating the symbol table, which is what the enterTrees() method does. Symbol table is a table composed of a set of symbol addresses and symbol information. The information registered in the symbol table is used at different stages of compilation. In semantic analysis, the content registered in symbol table will be used for semantic check and intermediate code generation. In the target code generation phase, when the symbolic name and address are assigned, the symbol table is the basis of address assignment. Symbol filling is implemented by com.sun.tools.javac.comp.Enter. The exit of this process is a To Do list, which contains the top-level nodes of the abstract syntax tree of each compilation unit and the top-level nodes of pack-info.java.

10.2.3 annotation processor

JDK 1.5 has support for annotation. JDK 1.6 provides a set of standard API of plug-in annotation processor to process annotation during compilation. We can regard it as a set of plug-ins during compilation. In these plug-ins, you can read, modify and add any element in the abstract syntax tree. If these plug-ins modify the syntax tree during annotation processing, the compilation period will return to the process of parsing and filling symbol table for reprocessing until all plug-in annotation processors have not modified the syntax tree again. Each cycle is called a Round.

The initialization process of the plug-in annotation processor is completed in the initProcessAnnotations() method, and its execution process is completed in the processAnnotations() method. This method determines whether there is a new annotation processor to execute. If there is one, use the doProcessing() of the com.sun.tools.javac.processing.JavacProcessingEnvironment class Method to generate a new JavaCompiler object for processing.

10.2.4 semantic analysis and bytecode generation

After the syntax analysis, the compiler obtains the abstract syntax tree representation of the program code. The syntax tree can represent the abstract of the source program with correct structure, but it cannot guarantee that the source program is logical.

The main task of semantic analysis is to review the context related properties of the source program with correct structure, such as type review, variable type assignment calculation conversion, etc.

1. Mark inspection

The semantic process is divided into two steps: annotation checking and data and control flow analysis, which are completed by attribute() and flow() respectively.

The contents of annotation check include whether variables are declared before use, whether data types between variables and assignments can match, etc. Annotation check is mainly completed by com.sun.tools.javac.comp.Attr and com.sun.tools.javac.comp.Check classes.

2. Data to control flow analysis

Data to control flow analysis is a further verification of program context logic. It can check such problems as whether there is assignment of program local variables before use, whether each path of method has return value, whether all checked exceptions have been correctly handled, etc. It is implemented by com.sun.tools.javac.comp.Flow class.

3. Paraphrase sugar

The syntax sugar in Java mainly refers to the generics, variable parameters, auto loading / unpacking and so on mentioned above. The virtual machine does not support these grammars. They need to revert back to the simple basic syntax structure in the compilation phase. This process is called parsing sugar. It is implemented by com.sun.tools.javac.comp.TransTypes and com.sun.tools.javac.comp.Lower classes.

4. Bytecode generation

It is done by the com.sun.tools.javac.jvm.Gen class. In the bytecode generation phase, not only the information generated in the previous steps is converted into bytecode and written to disk, but also a small amount of code addition and conversion is done by the compiler.

For example, the < init > () method and the < clinit > () method mentioned many times by signature are added to the syntax tree in this stage.

After traversing and adjusting the syntax tree, the symbol table filled with all the required information will be handed to the com.sun.tools.javac.jvm.ClassWriter Class, which will output the bytecode and generate the final Class file. This concludes the compilation process.

10.3 taste of Java syntax sugar

10.3.1 generics and type erasure

The essence of generics is the application of parameterized type, that is to say, the data type to be operated is specified as a parameter, which can be created on class, interface and method, which are called generics class, generics interface and generics method respectively.

Java generics have been replaced with raw type in the bytecode file after compilation, and the strong conversion code has been inserted in the corresponding place. So generics technology is actually a syntactic sugar of Java language. The method of generics implementation in Java language is called generics erasure, and the generics based on this method are called pseudo generics.

Due to the introduction of generics, JCP has made corresponding modifications to the virtual machine specification, and introduced new attributes such as Signature and LocalVariableTypeTable to solve the problem of parameter type identification accompanying generics It is an important attribute. Its function is to store the Signature of a method at the bytecode level. The parameter type saved by this attribute is not a native type, but includes the information of the parameter type.

Java code

package com.liukai.jvmaction.ch_10;

import java.util.List;

public class Test10 {

  public static void method() {
    System.out.println("m0");
  }

  public static void method(List<Integer> args) {
    System.out.println("m1");
  }

}

The following bytecode:

  public static void method(java.util.List<java.lang.Integer>);
    descriptor: (Ljava/util/List;)V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=2, locals=1, args_size=1
         0: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
         3: ldc           #6                  // String m1
         5: invokevirtual #5                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
         8: return
      LineNumberTable:
        line 16: 0
        line 17: 8
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       9     0  args   Ljava/util/List;
      LocalVariableTypeTable:
        Start  Length  Slot  Name   Signature
            0       9     0  args   Ljava/util/List<Ljava/lang/Integer;>;
    Signature: #26                          // (Ljava/util/List<Ljava/lang/Integer;>;)V

10.3.2 automatic packing, unpacking and traverse cycle

After compiling, auto boxing and unpacking are converted into corresponding packing and restoring methods, such as Integer.value() and Integer.intValue(), while traversal loop restores the code to the implementation of iterator. The variable length parameter becomes an array type parameter when called.

  // Traps for auto boxing
  public static void main(String[] args) {

    Integer a = 1;
    Integer b = 2;
    Integer c = 3;
    Integer d = 3;
    Integer e = 321;
    Integer f = 321;
    Long g = 3L;

    System.out.println(c == d);// true
    System.out.println(e == f);// false
    System.out.println(c == (a + b));// true
    System.out.println(c.equals(a + b));//true
    // The "= =" operation of the wrapper class will not be unpacked automatically without encountering the arithmetic operation, and their equals() method does not handle the concerns of data transformation
    System.out.println(g == (a + b));// true
    System.out.println(g.equals(a + b));// false
  }

Note that "= =" operations of wrapper classes will not be unpacked automatically without encountering arithmetic operations, and their equals() method does not handle data transformation concerns.

10.3.3 conditional compilation

Conditional compilation is conditional selective compilation. In Java, conditional compilation is based on if statements with conditions as constants.

  public static void conditionCompiler() {
    if(true){
      System.out.println(1);
    }else{
      System.out.println(2);
    }
  }

Compiled bytecode file

  public static void conditionCompiler();
    descriptor: ()V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=2, locals=0, args_size=0
         // We found that only the bytecode of System.out.println(1); was compiled
         0: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
         3: iconst_1
         4: invokevirtual #3                  // Method java/io/PrintStream.println:(I)V
         7: return
      LineNumberTable:
        line 30: 0
        line 34: 7

Through the above code, we find that only the bytecode instructions of System.out.println(1); are compiled in bytecode.

10.4 practice: plug in annotation processor

10.4.1 actual objective

Through the analysis of Javac source code, until the compiler compiles the Java source code into bytecode, we will check and verify the Java program source code. In view of this, there are many auxiliary verification tools for "well written" programs in the industry, such as CheckStyle, FindBug, Klocwork, etc. Some of these code verification tools are based on Java source code, and others are completed by scanning byte code. In this section, we will use the annotation processor API to write a verification tool with our own coding style: NameCheckProcessor.

According to the requirements of Java language specification, the command of Java program shall conform to the writing specification of the following format:

  • Class (or interface): conform to hump naming and initial capitalization.
  • Method: according to the order of hump, the first letter is lowercase.
  • Field:
    • Class or instance variable: conforms to the hump command, with the initial in lowercase.
    • Constant: all uppercase letters or underscores are required. The first letter cannot be underscores.

Our actual goal is to add an additional function for Javac compiler, and check whether the program name meets the above command requirements for class, interface, method and field when programming the program.

10.4.2 code implementation

To implement a compiler plug-in through the annotation processor API, the code of annotation processor needs to inherit the abstract class javax.annotation.processing.AbstractProcessor.

The first parameter "annotations" can get the annotation set to be processed by the annotation processor, and the second parameter "roundEnv" can access the syntax tree node in the current Round. Each syntax tree node represents a Element. It includes the most commonly used elements in Java.

It also implements an initialization method init(), whose parameter processingEnv represents a context environment provided by the annotation processor framework. This instance variable is needed to create new code, output information to the compiler, obtain other tool classes, etc.

In addition to the process() method and its parameters, annotation processors also have two Annotations that can be used together: @ SupportedAnnotationTypes and @ SupportSourceVersion. The former represents which Annotations the processor is interested in, and "*" can be used as a wildcard to represent all Annotations. The latter refers to which versions the annotation processor can handle Java code.

Each annotation processor is singleton at runtime. If there is no need to change or generate the content of the syntax tree, the process() method can return a false Boolean value, telling the compiler that the code in the Round has not changed, and it is not necessary to construct a new JavaCompiler instance.

Below is given

package com.liukai.jvmaction.ch_10;

import javax.annotation.processing.*;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.Element;
import javax.lang.model.element.TypeElement;
import java.util.Set;

/**
 * 10-11 Annotation processor NameCheckProcessor
 */
//All Annotations can be supported with "*", wildcards can be used, and {} can be used for segmentation if different packages are needed
@SupportedAnnotationTypes("*")
//Fill in the supported java version here
@SupportedSourceVersion(SourceVersion.RELEASE_8)
public class NameCheckProcessor extends AbstractProcessor {

  private NameChecker nameChecker;

  /**
   * Initialize name checking plug-in, processingEnv provides context environment for annotation processor
   */
  @Override
  public void init(ProcessingEnvironment processingEnv) {
    super.init(processingEnv);
    nameChecker = new NameChecker(processingEnv);
  }

  /**
   * Check the name of each node of the input syntax tree
   */
  @Override
  public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
    if (!roundEnv.processingOver()) {
      for (Element element : roundEnv.getRootElements())
      //Here you can do what you want
      {
        nameChecker.checkNames(element);
      }
    }
    return false;
  }

}

Code for NameChecker checker:

package com.liukai.jvmaction.ch_10;

import javax.annotation.processing.Messager;
import javax.annotation.processing.ProcessingEnvironment;
import javax.lang.model.element.*;
import javax.lang.model.util.ElementScanner8;
import javax.tools.Diagnostic.Kind;
import java.util.EnumSet;

/**
 * 10-12 Command inspector
 */
public class NameChecker {

  private final Messager messager;

  NameCheckScanner nameCheckScanner = new NameCheckScanner();

  public NameChecker(ProcessingEnvironment processingEnv) {

    this.messager = processingEnv.getMessager();
  }

  /**
   * Check the naming of java programs. The naming of java names should conform to the following format:
   * <ul>
   * <li>Class or interface: in accordance with camel nomenclature, initial capital
   * <li>Method: in accordance with camel nomenclature, the first letter is lowercase
   * <li>Field: camel like nomenclature, lowercase initial
   * <li>Class and instance variables: camel like nomenclature, lowercase initial
   * <li>Constant: requires all uppercase
   * </ul>
   */
  public void checkNames(Element element) {
    nameCheckScanner.scan(element);

  }

  /**
   * The name checker implementation class inherits the newly provided elementscanner8 in jdk1.8 < br >
   * Elements in the abstract syntax tree will be accessed in Visitor mode
   */
  private class NameCheckScanner extends ElementScanner8<Void, Void> {

    /**
     * This method is used to check java classes
     */
    @Override
    public Void visitType(TypeElement e, Void p) {

      scan(e.getTypeParameters(), p);
      checkCamelCase(e, true);
      super.visitType(e, p);
      return null;
    }

    /**
     * Check whether the method name is legal
     */
    @Override
    public Void visitExecutable(ExecutableElement e, Void p) {

      if (e.getKind() == ElementKind.METHOD) {
        Name name = e.getSimpleName();
        if (name.contentEquals(e.getEnclosingElement().getSimpleName())) {
          messager.printMessage(Kind.WARNING, " A common method '" + name + "' Should not be the same as the class name to avoid confusion with the constructor", e);
        }
        checkCamelCase(e, false);
      }

      super.visitExecutable(e, p);
      return null;
    }

    /**
     * Check whether variable naming is legal
     */
    @Override
    public Void visitVariable(VariableElement e, Void p) {

      if (e.getKind() == ElementKind.ENUM_CONSTANT || e.getConstantValue() != null
        || heuristicallyConstant(e)) {
        checkAllCaps(e);
      } else {
        checkCamelCase(e, false);
      }

      return null;
    }

    /**
     * Determine whether a variable is a constant
     *
     * @param e
     * @return
     */
    private boolean heuristicallyConstant(VariableElement e) {

      if (e.getEnclosingElement().getKind() == ElementKind.INTERFACE) {
        return true;
      } else if (e.getKind() == ElementKind.FIELD && e.getModifiers()
        .containsAll(EnumSet.of(Modifier.PUBLIC, Modifier.STATIC, Modifier.FINAL))) {
        return true;
      } else {
        return false;
      }
    }

    /**
     * Check whether the incoming Element conforms to camel nomenclature, and output warning information if not
     *
     * @param e
     * @param initialCaps
     */
    private void checkCamelCase(Element e, boolean initialCaps) {

      String name = e.getSimpleName().toString();
      boolean previousUpper = false;
      boolean conventional = true;

      int firstCodePoint = name.codePointAt(0);

      if (Character.isUpperCase(firstCodePoint)) {
        previousUpper = true;
        if (!initialCaps) {
          messager.printMessage(Kind.WARNING, "Name  '" + name + " ' Should start with a lowercase letter", e);
          return;
        }
      } else if (Character.isLowerCase(firstCodePoint)) {
        if (initialCaps) {
          messager.printMessage(Kind.WARNING, "Name  '" + name + " ' Should start with a capital letter", e);
          return;
        }
      } else {
        conventional = false;
      }

      if (conventional) {
        int cp = firstCodePoint;
        for (int i = Character.charCount(cp); i < name.length(); i += Character.charCount(cp)) {
          cp = name.codePointAt(i);
          if (Character.isUpperCase(cp)) {
            if (previousUpper) {
              conventional = false;
              break;
            }
            previousUpper = true;
          } else {
            previousUpper = false;
          }
        }

      }
      if (!conventional) {
        messager.printMessage(Kind.WARNING, "Name  '" + name + " ' It should conform to camel nomenclature", e);
      }

    }

    /**
     * Capital naming check requires that the first letter is a capital English letter and the rest is a capital letter or underscore
     *
     * @param e
     */
    private void checkAllCaps(VariableElement e) {

      String name = e.getSimpleName().toString();
      boolean conventional = true;

      int firstCodePoint = name.codePointAt(0);

      if (!Character.isUpperCase(firstCodePoint)) {
        conventional = false;
      } else {
        boolean previousUnderscore = false;

        int cp = firstCodePoint;
        for (int i = Character.charCount(cp); i < name.length(); i += Character.charCount(cp)) {
          cp = name.codePointAt(i);
          if (cp == '_') {
            if (previousUnderscore) {
              conventional = false;
              break;
            }
            previousUnderscore = true;
          } else {
            previousUnderscore = false;
            if (!Character.isUpperCase(cp) && !Character.isDigit(cp)) {
              conventional = false;
              break;
            }
          }
        }
      }
      if (!conventional) {
        messager.printMessage(Kind.WARNING, "constant  '" + name + " ' It should all be named with capital letters or underscores and begin with letters", e);
      }
    }

  }

}

In this class, a NameCheckScanner class inherited from javax.lang.model.util.ElementScanner8 is used to traverse the syntax tree in Visitor mode. visitType(), visitVariable(), visitexecutible() methods are executed to access classes, fields and methods, and their naming rules are checked accordingly.

package com.liukai.jvmaction.ch_10;

/**
 * 10-13 Code samples with multiple irregular naming
 */
public class BADLY_NAME_CODE {

  static final int _FORTY_TWO = 42;

  public static int NOT_A_CONSTANT = _FORTY_TWO;

  protected void BADLY_NAMED_CODE() {
    return;
  }

  public void NOTCASEmethodNAME() {
    return;
  }

  enum colors {
    red, blue, green;
  }

}

10.4.3 operation and test

We use the "- processor" parameter of javac command to execute the annotation processor attached when compiling. Multiple annotation processors are separated by commas. You can also use the - xprintroutes and - XprintProcessorInfo parameters to see the details of the processor's operation.

# Enter the package root path of the project
cd /Users/liukai/IdeaProjects/myproject/jvm-action/src/main/java

# Compiling NameChecker.java file with javac command
javac com/liukai/jvmaction/ch_10/NameChecker.java

# Compile NameCheckProcessor.java file with javac command
javac com/liukai/jvmaction/ch_10/NameCheckProcessor.java

# Compile the badly? Name? Code.java file using the javac command
javac -processor com.liukai.jvmaction.ch_10.NameCheckProcessor com/liukai/jvmaction/ch_10/BADLY_NAME_CODE.java

//Output results:
liukai•src/main/java(master⚡)» javac -processor com.liukai.jvmaction.ch_10.NameCheckProcessor com/liukai/jvmaction/ch_10/BADLY_NAME_CODE.java                                                         [15:14:12]
com/liukai/jvmaction/ch_10/BADLY_NAME_CODE.java:6: warning: Name  'BADLY_NAME_CODE ' It should conform to camel nomenclature
public class BADLY_NAME_CODE {
       ^
com/liukai/jvmaction/ch_10/BADLY_NAME_CODE.java:20: warning: Name  'colors ' Should start with a capital letter
  enum colors {
  ^
com/liukai/jvmaction/ch_10/BADLY_NAME_CODE.java:21: warning: constant  'red ' It should all be named with capital letters or underscores and begin with letters
    red, blue, green;
    ^
com/liukai/jvmaction/ch_10/BADLY_NAME_CODE.java:21: warning: constant  'blue ' It should all be named with capital letters or underscores and begin with letters
    red, blue, green;
         ^
com/liukai/jvmaction/ch_10/BADLY_NAME_CODE.java:21: warning: constant  'green ' It should all be named with capital letters or underscores and begin with letters
    red, blue, green;
               ^
com/liukai/jvmaction/ch_10/BADLY_NAME_CODE.java:8: warning: constant  '_FORTY_TWO ' It should all be named with capital letters or underscores and begin with letters
  static final int _FORTY_TWO = 42;
                   ^
com/liukai/jvmaction/ch_10/BADLY_NAME_CODE.java:10: warning: Name  'NOT_A_CONSTANT ' Should start with a lowercase letter
  public static int NOT_A_CONSTANT = _FORTY_TWO;
                    ^
com/liukai/jvmaction/ch_10/BADLY_NAME_CODE.java:12: warning: Name  'BADLY_NAMED_CODE ' Should start with a lowercase letter
  protected void BADLY_NAMED_CODE() {
                 ^
com/liukai/jvmaction/ch_10/BADLY_NAME_CODE.java:16: warning: Name  'NOTCASEmethodNAME ' Should start with a lowercase letter
  public void NOTCASEmethodNAME() {
              ^
9 A warning

10.4.4 other application cases

The actual example of NameCheckProcessor only shows some functions of JSR-269 embedded annotation processor API. Based on this set of API, there are also projects supported by Hibernate Validator Annotation Processor to verify the correctness of Hibernate tag usage, and projects to automatically generate getter and setter methods for fields Lombok, etc.

10.4.5 analysis and comparison of common Java static code analysis tools

The annotation processor described in the previous chapter is used in the javac compiler, which can check, analyze and modify the syntax tree loaded by the source code. Next, we introduce the static code scanning tools, which are not based on the annotation processor, but do things similar to and more powerful than the actual examples we wrote above.

Checkstyle

Checkstyle was produced in 2001. It is a static source code analysis tool using antlr as a java parser. The xml configuration file of checkstyle allows you to specify source code analysis rules. New code checking logic can be implemented by inheriting checkstyle's own Check. In addition, inheriting AbstractFileSetCheck can implement the Check rules of other programming languages except java. However, antlr encapsulated by checkstyle can only analyze java syntax, and does not encapsulate other parsers. Therefore, if you want to Check the code of other languages with checkstyle, you need to encapsulate or implement the syntax analysis tools of corresponding languages.

ANTLR

Code checking tool selection

PMD

PMD was produced in 2002. It is a static source code analysis tool using JavaCC as a java parser. PMD first parses the syntax tree of java source code (PMD maintains the syntax tree in xml format), and then traverses the syntax tree for code checking, except that the java parser is JavaCC.

JavaCC is a lexical analysis generator and syntax analysis generator. Lexical analysis and syntax analysis are software components for processing input character sequences. Compiler and interpreter cooperate with lexical analysis and syntax analysis to "decrypt" program files. It is a separate project and has no direct relationship with the javac compiler in the jdk. Java C compiler has its own lexical analysis and parser.

JavaCC detailed explanation

findbugs

Findbugs was born in 2003. It is a code checking tool based on bcel library to complete code checking by scanning bytecode. Any source file that can be compiled into bytecode can be checked by findbugs, but you need to have a good understanding of bcel library and bytecode.

Alibaba's code specification plug-in is Alibaba Java Coding Guidelines

Alibaba's code specification plug-in is based on PMD PMD is a code static analysis tool. When PMD rules are used to analyze java source code, PMD first uses JavaCC and EBNF grammars to generate a syntax analyzer to analyze java code in common text form and generate syntax that conforms to specific syntax structure. At the same time, the concept of semantics, JJTree, is added on the basis of JavaCC through a conversion of JJTree, In this way, java code is transformed into an AST, which is the semantic layer above the Java symbol stream, and PMD processes the AST into a symbol table. Then write PMD rules. A PMD rule can be regarded as a Visitor. Through traversing AST, find out a specific pattern among multiple objects, that is, the problems of the code.

Alibaba Java code specification plug-in P3C PMD use guide and implementation analysis

Analysis and comparison of common Java static code analysis tools

Java static analysis tool Analysis object applied technology
Checkstyle Java source files Defect pattern matching
FindBugs Bytecode Defect pattern matching; data flow analysis
PMD Java source code Defect pattern matching
Jtest Java source code Defect pattern matching; data flow analysis

We can install these plug-ins to check code specifications in development tool idea, such as Alibaba Java Coding Guidelines, checkstyle plug-ins, sonarlint plug-ins, etc.

Tags: Programming Java JDK Attribute jvm

Posted on Tue, 10 Mar 2020 05:37:11 -0400 by lordofgore