首页 > 其他分享 >JVM之字节码的编译原理

JVM之字节码的编译原理

时间:2023-08-12 19:00:39浏览次数:38  
标签:Java 字节 编译器 pos 语法 编译 token JVM 解析

JVM之字节码的编译原理

Java最初诞生的目的就是为了在不依赖特定的物理硬件和操作系统环境下运行,那么也就是说Java程序实现跨平台我的基石其实就是字节码。

Java之所以能够解决程序的安全性问题、跨平台移植性等问题,最主要的原因就是Java源代码的编译结果并非是本地机器指令,而是字节码。当Java源代码成功编译成字节码后,如果想在不同的平台上面运行,则无需再次编译,也就是说Java源码只需一次编译就可处处运行,这就是**“Write Once,Run Anywhere”**的思想。

简单来说,字节码就相当于是一份通用的契约,尽管不同平台上的Java虚拟机的实现细节不尽相同,但是它们共同执行的字节码内容却是一样的。

javac编译器简介

JIT编译

Java程序最初是仅仅通过解释器解释执行的,即对字节码逐条解释执行,这种方式的执行速度相对会比较慢,尤其当某个方法或代码块运行的特别频繁时,这种方式的执行效率就显得很低。于是后来在虚拟机中引入了JIT编译器(即时编译器),当虚拟机发现某个方法或代码块运行特别频繁时,就会把这些代码认定为“Hot Spot Code”(热点代码),为了提高热点代码的执行效率,在运行时,虚拟机将会把这些代码编译成与本地平台相关的机器码,并进行各层次的优化,完成这项任务的正是JIT编译器。

Java源码的编译结果是字节码,那么肯定需要有一种编译器能够将Java源码编译为字节码,承担这个重任的就是配置在**“PATH”**环境变量中的javac编译器,javac是一种能够将Java源码编译为字节码的前端编译器,其他的前端编译器还有诸如Eclipse JDT中的增量式编译器ECJ等。相对应的还有后端编译器,它在程序运行期间将字节码转变成机器码(现在的Java程序在运行时基本都是解释执行加编译执行),如HotSpot虚拟机自带的JIT(Just In Time Compiler)编译器(分Client端和Server端)

Javac与Eclipse Compiler for Java编译器

Hotspot VM并没有强制要求前端编译器只能使用 javac来编译字节码,其实只要编译结果符合JVM规范都可以被JVM所识别,所以在Java的前端编译器领域,除了 Javac之外, 还有一种被大家经常用到的前端编译器,那就是内置在 Eclipse中的ECJ( Eclipse Compiler for Java)编译器。相信有不少开发人员会误以为 Eclipse中同样也是使用JDK或者 OpenJDK 中的 Javac编译器来编译字节码,其实不是这样的, Eclipse中所使用的ECJ前端编译器完全 是自主研发的,并且可以和 Javac相媲美,甚至可以比 Javac更优秀。

和 Javac的全量式编译不同,ECJ是一种增量式编译器。在 Eclipse中,当开发人员编写 完代码后,使用“Ctrl+S”快捷键时,ECJ编译器所采取的编译方案是把未编译部分的源码 逐行进行编译,而非每次都全量编译。因此ECJ的编译效率会比 Javac更加迅速和高效,当 然编译质量和 javac相比其实大致还是一样的。这里大家需要注意,前端编译器并不会直接涉及编译优化等方面的技术,而是将这些具体优化细节移交给 Hotspot I的JIT编译器负责

编译原理

Javac编译器在 将Java源码编译为一个有效的字节码文件,主要会经历4个步骤,分别是:词法解析→语法解析→语义解析→生成字节码

JVM并不会与Java语言“终生绑定”,任何语言编写的程序 都可以运行在JVM中,前提是源码的编译结果满足并包含Java虚拟机的内部指令集、符号表 以及其他的辅助信息,它就是一个有效的字节码文件,就能够被虚拟机所识别并装载运行。

当弄清楚这些基本概念之后,接下来我们再来了解 Javac的编译原理,其实所谓编译原 理,大家可以理解为Java源码编译为字节码时所需要经历的一些编译步骤。 Javac编译器在 将Java源码编译为一个有效的字节码文件,主要会经历4个步骤,分别是:词法解析→语法解析→语义解析→生成字节码,如图所示

image-20210509125953581.png upload successful

第一步:词法解析

什么是词法解析?

编译的第一步是词法解析,那么究竟什么是词法解析呢?在Java语言中,关键字相信 大家都非常熟悉,所谓关键字指的就是 Java API内部预定义的一些字符集合(如 public、 private、 class 等)。那么词法解析要做的事情就是将Java源码中的关键字和标示符等内容转换为符合Java语法规范的 Token序列,然后按照指定的顺序规则进行匹配校验,这就是词法解析步骤。

词法分析是将源代码的字符流转变为标记(Token)集合。单个字符是程序编写过程中的的最小元素,而标记则是编译过程的最小元素,关键字、变量名、字面量、运算符等都可以成为标记,比如整型标志int由三个字符构成,但是它只是一个标记,不可拆分。

1、词法解析步骤

在javac编译器中,词法解析器接口是com.sun.tools.javac.parser.Lexer,它的直接派生实现是位于同包下的Scanner类,该类的对象实例由ScannerFactory工厂负责创建。Scanner类的主要任务就是按照单个字符的方式读取Java源文件中的关键字和标示符等内容,然后将其转换为符合Java语法规范的Token序列。在这里大家要注意,负责词法解析工作的并不是Scanner类,而是com.sun.tools.javac.parser.JavacParser类,该类的对象实例由ParserFactory工厂负责创建,也就是说,由JavacParser类负责控制词法解析时的具体细节,而Scanner类仅仅只是负责读取源码中的字符集合以及与Token序列之间的转换任务,如图2-3所示

upload successful

首先由 compile方法调用com.sun. tools. javac. main. javacompiler类的 parseFiles()方法, 该方法的主要任务就是调用 parse()方法获取 Javacparser类的对象实例,然后调用 Javacparser 类的 parseCompilationUnit()方法执行词法解析,如下所示:

upload successful

2、Token序列

词法解析器在成功读取到Java源码中的关键字和标示符等内容后,会将其转换为符合Java语法规范和Token序列,那么究竟什么是Token呢?实际上Token其实就是一个枚举类型,内部定义了许多符合Java语法规范并与源码字符相对应的枚举常量。你可以在com.sun.tools.javac.parser.Token类中找到所有枚举常量,如下所示

EOF(),
ERROR(),
IDENTIFIER(Tag.NAMED),
ABSTRACT("abstract"),
ASSERT("assert", Tag.NAMED),
BOOLEAN("boolean", Tag.NAMED),
BREAK("break"),
BYTE("byte", Tag.NAMED),
CASE("case"),
CATCH("catch"),
CHAR("char", Tag.NAMED),
CLASS("class"),
CONST("const"),
CONTINUE("continue"),
DEFAULT("default"),
DO("do"),
DOUBLE("double", Tag.NAMED),
ELSE("else"),
ENUM("enum", Tag.NAMED),
EXTENDS("extends"),
FINAL("final"),
FINALLY("finally"),
FLOAT("float", Tag.NAMED),
FOR("for"),
GOTO("goto"),
IF("if"),
IMPLEMENTS("implements"),
IMPORT("import"),
INSTANCEOF("instanceof"),
INT("int", Tag.NAMED),
INTERFACE("interface"),
LONG("long", Tag.NAMED),
NATIVE("native"),
NEW("new"),
PACKAGE("package"),
PRIVATE("private"),
PROTECTED("protected"),
PUBLIC("public"),
RETURN("return"),
SHORT("short", Tag.NAMED),
STATIC("static"),
STRICTFP("strictfp"),
SUPER("super", Tag.NAMED),
SWITCH("switch"),
SYNCHRONIZED("synchronized"),
THIS("this", Tag.NAMED),
THROW("throw"),
THROWS("throws"),
TRANSIENT("transient"),
TRY("try"),
VOID("void", Tag.NAMED),
VOLATILE("volatile"),
WHILE("while"),
INTLITERAL(Tag.NUMERIC),
LONGLITERAL(Tag.NUMERIC),
FLOATLITERAL(Tag.NUMERIC),
DOUBLELITERAL(Tag.NUMERIC),
CHARLITERAL(Tag.NUMERIC),
STRINGLITERAL(Tag.STRING),
TRUE("true", Tag.NAMED),
FALSE("false", Tag.NAMED),
NULL("null", Tag.NAMED),
UNDERSCORE("_", Tag.NAMED),
ARROW("->"),
COLCOL("::"),
LPAREN("("),
RPAREN(")"),
LBRACE("{"),
RBRACE("}"),
LBRACKET("["),
RBRACKET("]"),
SEMI(";"),
COMMA(","),
DOT("."),
ELLIPSIS("..."),
EQ("="),
GT(">"),
LT("<"),
BANG("!"),
TILDE("~"),
QUES("?"),
COLON(":"),
EQEQ("=="),
LTEQ("<="),
GTEQ(">="),
BANGEQ("!="),
AMPAMP("&&"),
BARBAR("||"),
PLUSPLUS("++"),
SUBSUB("--"),
PLUS("+"),
SUB("-"),
STAR("*"),
SLASH("/"),
AMP("&"),
BAR("|"),
CARET("^"),
PERCENT("%"),
LTLT("<<"),
GTGT(">>"),
GTGTGT(">>>"),
PLUSEQ("+="),
SUBEQ("-="),
STAREQ("*="),
SLASHEQ("/="),
AMPEQ("&="),
BAREQ("|="),
CARETEQ("^="),
PERCENTEQ("%="),
LTLTEQ("<<="),
GTGTEQ(">>="),
GTGTGTEQ(">>>="),
MONKEYS_AT("@"),
CUSTOM;
3、源码字符集合与Token对应关系

词法解析器在将源码字符集合转换为Token之前,会先将每一个字符集合都转换为一个对应的Name对象,每一个源码字符命令就是一个Name对象,然后再由com.sun.tools.javac.parser.Keywords类负责实际的Token转换工作,对应关系奖会存在Keywords类的数组key中

// 通过 Names 调用 Name 的 fromChars() 方法获取出 Name对象
name = names.fromChars(sbuf, 0, sp);
// 根据 Name 对象获取出与之对应的 Token 
token = keywords.key(name);

调用key()方法获取指定Token

public Token key(Name name) {
  return (name.getIndex() > maxKey) ? IDENTIFIER : key[name.getIndex()];
}
4、调用parseCompilationUnit执行词法解析
public JCTree.JCCompilationUnit parseCompilationUnit() {
    Token firstToken = token;
    JCModifiers mods = null;
    boolean consumedToplevelDoc = false;
    boolean seenImport = false;
    boolean seenPackage = false;
    ListBuffer<JCTree> defs = new ListBuffer<>();
    if (token.kind == MONKEYS_AT)
        mods = modifiersOpt();

    if (token.kind == PACKAGE) {
        int packagePos = token.pos;
        List<JCAnnotation> annotations = List.nil();
        seenPackage = true;
        if (mods != null) {
            checkNoMods(mods.flags);
            annotations = mods.annotations;
            mods = null;
        }
        nextToken();
        JCExpression pid = qualident(false);
        accept(SEMI);
        JCPackageDecl pd = toP(F.at(packagePos).PackageDecl(annotations, pid));
        attach(pd, firstToken.comment(CommentStyle.JAVADOC));
        consumedToplevelDoc = true;
        defs.append(pd);
    }

    boolean checkForImports = true;
    boolean firstTypeDecl = true;
    while (token.kind != EOF) {
        if (token.pos <= endPosTable.errorEndPos) {
            // error recovery
            skip(checkForImports, false, false, false);
            if (token.kind == EOF)
                break;
        }
        if (checkForImports && mods == null && token.kind == IMPORT) {
            seenImport = true;
            defs.append(importDeclaration());
        } else {
            Comment docComment = token.comment(CommentStyle.JAVADOC);
            if (firstTypeDecl && !seenImport && !seenPackage) {
                docComment = firstToken.comment(CommentStyle.JAVADOC);
                consumedToplevelDoc = true;
            }
            if (mods != null || token.kind != SEMI)
                mods = modifiersOpt(mods);
            if (firstTypeDecl && token.kind == IDENTIFIER) {
                ModuleKind kind = ModuleKind.STRONG;
                if (token.name() == names.open) {
                    kind = ModuleKind.OPEN;
                    nextToken();
                }
                if (token.kind == IDENTIFIER && token.name() == names.module) {
                    if (mods != null) {
                        checkNoMods(mods.flags & ~Flags.DEPRECATED);
                    }
                    defs.append(moduleDecl(mods, kind, docComment));
                    consumedToplevelDoc = true;
                    break;
                } else if (kind != ModuleKind.STRONG) {
                    reportSyntaxError(token.pos, Errors.ExpectedModule);
                }
            }
            JCTree def = typeDeclaration(mods, docComment);
            if (def instanceof JCExpressionStatement)
                def = ((JCExpressionStatement)def).expr;
            defs.append(def);
            if (def instanceof JCClassDecl)
                checkForImports = false;
            mods = null;
            firstTypeDecl = false;
        }
    }
    JCTree.JCCompilationUnit toplevel = F.at(firstToken.pos).TopLevel(defs.toList());
    if (!consumedToplevelDoc)
        attach(toplevel, firstToken.comment(CommentStyle.JAVADOC));
    if (defs.isEmpty())
        storeEnd(toplevel, S.prevToken().endPos);
    if (keepDocComments)
        toplevel.docComments = docComments;
    if (keepLineMap)
        toplevel.lineMap = S.getLineMap();
    this.endPosTable.setParser(null); // remove reference to parser
    toplevel.endPositions = this.endPosTable;
    return toplevel;
}

第二步:语法解析

当词法解析结束后,javac就会进入到编译的第二个阶段,也就是语法解析。所谓语法解析指的就是将词法解析后的Token序列整合为一棵结构化的抽象语法树,因为我们知道,一个try语句后面肯定会接上一个catch或者finally子句,这就是语法解析步骤。

语法分析是根据Token序列来构造抽象语法树的过程。抽象语法树是一种用来描述程序代码语法结构的树形表示方式,语法树的每一个节点都代表着程序代码中的一个语法结构,如bao、类型、修饰符、运算符等。经过这个步骤后,编译器就基本不会再对源码文件进行操作了,后续的操作都建立在抽象语法树之上。

1、调用qualident()方法解析package语法节点

当词法解析器成功地将package关键字声明转换为Token并完成词法解析后,就会调用qualident()方法根据Token.PACKAGE解析为package语法节点

public JCExpression qualident(boolean allowAnnos) {
    // 解析为JCIdent语法节点
    JCExpression t = toP(F.at(token.pos).Ident(ident()));
    while (token.kind == DOT) {
        int pos = token.pos;
        nextToken();
        List<JCAnnotation> tyannos = null;
        if (allowAnnos) {
            tyannos = typeAnnotationsOpt();
        }
      	// 解析为JCFieldAccess语法节点
        t = toP(F.at(pos).Select(t, ident()));
        if (tyannos != null && tyannos.nonEmpty()) {
            t = toP(F.at(tyannos.head.pos).AnnotatedType(tyannos, t));
        }
    }
    return t;
}

Ident()方法如下

public JCIdent Ident(Name name) {
    // 根据Name对象解析出一个JCIdent语法节点
    JCIdent tree = new JCIdent(name, null);
    tree.pos = pos;
    return tree;
}

Select()方法如下

public JCFieldAccess Select(JCExpression selected, Name selector) {
       // 根据Name对象解析出嵌套JCFieldAccess语法节点
       JCFieldAccess tree = new JCFieldAccess(selected, selector, null);
       tree.pos = pos;
       return tree;
   }

观察 Ident() 与 **Select()**方法你会发现它们的形参都是Name,所以在解析语法树或者语法节点时,首先需要将Token转换为对应的Name对象。在解析JCIdent语法节点的Ident()方法参数中调用了ident()方法,该方法会返回一个与Token对应的Name对象,如下

protected Name ident(boolean advanceOnErrors) {
        if (token.kind == IDENTIFIER) {
            Name name = token.name();
            nextToken();
            return name;
        } else if (token.kind == ASSERT) {
            log.error(DiagnosticFlag.SYNTAX, token.pos, Errors.AssertAsIdentifier);
            nextToken();
            return names.error;
        } else if (token.kind == ENUM) {
            log.error(DiagnosticFlag.SYNTAX, token.pos, Errors.EnumAsIdentifier);
            nextToken();
            return names.error;
        } else if (token.kind == THIS) {
            if (allowThisIdent) {
                // Make sure we're using a supported source version.
                checkSourceLevel(Feature.TYPE_ANNOTATIONS);
                Name name = token.name();
                nextToken();
                return name;
            } else {
                log.error(DiagnosticFlag.SYNTAX, token.pos, Errors.ThisAsIdentifier);
                nextToken();
                return names.error;
            }
        } else if (token.kind == UNDERSCORE) {
            if (Feature.UNDERSCORE_IDENTIFIER.allowedInSource(source)) {
                log.warning(token.pos, Warnings.UnderscoreAsIdentifier);
            } else {
                log.error(DiagnosticFlag.SYNTAX, token.pos, Errors.UnderscoreAsIdentifier);
            }
            Name name = token.name();
            nextToken();
            return name;
        } else {
            accept(IDENTIFIER);
            if (advanceOnErrors) {
                nextToken();
            }
            return names.error;
        }
    }
2、调用importDeclaration()方法解析import语法树

当成功解析package语法节点后,语法解析的下一步就是解析import语法树。编译器在执行词法解析过程中,会匹配当前Token是否是Token.IMPORT,如果Token匹配成功,parseCompilationUnit()方法就会调用importDeclaration()方法根据Token.IMPORT解析为import语法树。源码如下

protected JCTree importDeclaration() {
        int pos = token.pos;
        nextToken();
        boolean importStatic = false;
  			// 匹配Token.STATIC
        if (token.kind == STATIC) {
            importStatic = true;
            nextToken();
        }
  			// 根据Name 对象解析出一个JCIdent语法节点
        JCExpression pid = toP(F.at(token.pos).Ident(ident()));
        do {
            int pos1 = token.pos;
            accept(DOT);
            if (token.kind == STAR) {
                pid = to(F.at(pos1).Select(pid, names.asterisk));
                nextToken();
                break;
            } else {
                pid = toP(F.at(pos1).Select(pid, ident()));
            }
        } while (token.kind == DOT);
        accept(SEMI);
  			// 将JCIdent和JCFieldAccess语法节点整合为一个JCImport语法树
        return toP(F.at(pos).Import(pid, importStatic));
    }

上述代码示例中,首先会匹配 Token. STATIC,用于检测 Impor关键字声明中是否包含 static静态导入。接下来 importDeclaration()方法便会调用语法解析器的Ident()方法解析出一 个 JCIdent语法节点,如果 Import关键字声明中定义多级目录时,则会调用语法解析器的 Select()方法,将其解析为嵌套的 JCFieldAccess 语法节点,这和之前解析 package语法节点 是一样的。 ​ 当语法解析器成功解析 JCIdent和 JCFieldAccess i语法节点后, importDeclaration()方法 就会调用语法解析器的 Import()方法,将之前解析过的语法节点整合为一棵 JCImport语法 树,如下所示:

public JCImport Import(JCTree qualid, boolean importStatic) {
			// 解析 JCImport语法树
  			JCImport tree = new JCImport(qualid, importStatic);
       tree.pos = pos;
       return tree;
   }

实际开发过程中,可能会有多个import关键字声明,那么parseCompilationUnit()方法内部则会通过循环迭代的方式解析JCImport语法树,然后将其存储在一个集合中。

3、调用classDeclaration()方法解析class语法树

在import解析并合并为JCImport语法树后,在parseCompilationUnit()方法内部就会通过typeDeclatation()方法调用classOrInterfaceOrEnumDeclaration()方法将class主体信息解析为一棵JCClassDecl语法树。如下所示:

protected JCStatement classOrInterfaceOrEnumDeclaration(JCModifiers mods, Comment dc) {
        if (token.kind == CLASS) {
          	// 将class类型解析为一棵JCClassDecl 语法树
            return classDeclaration(mods, dc);
        } else if (token.kind == INTERFACE) {
            // 将interface类型解析为一棵JCClassDecl 语法树
            return interfaceDeclaration(mods, dc);
        } else if (token.kind == ENUM) {
          	// 将枚举类型解析为一棵JCClassDecl语法树
            return enumDeclaration(mods, dc);
        } else {
            int pos = token.pos;
            List<JCTree> errs;
            if (LAX_IDENTIFIER.accepts(token.kind)) {
                errs = List.of(mods, toP(F.at(pos).Ident(ident())));
                setErrorEndPos(token.pos);
            } else {
                errs = List.of(mods);
            }
            final JCErroneous erroneousTree;
            if (parseModuleInfo) {
                erroneousTree = syntaxError(pos, errs, Errors.ExpectedModuleOrOpen);
            } else {
                erroneousTree = syntaxError(pos, errs, Errors.Expected3(CLASS, INTERFACE, ENUM));
            }
            return toP(F.Exec(erroneousTree));
        }
    }

classDeclatation()方法如下

protected JCClassDecl classDeclaration(JCModifiers mods, Comment dc) {
        int pos = token.pos;
        accept(CLASS);
        Name name = typeName();

        List<JCTypeParameter> typarams = typeParametersOpt();

        JCExpression extending = null;
        if (token.kind == EXTENDS) {
            nextToken();
            extending = parseType();
        }
        List<JCExpression> implementing = List.nil();
        if (token.kind == IMPLEMENTS) {
            nextToken();
            implementing = typeList();
        }
        //解析类中的所有成员信息,并存储在集合中
        List<JCTree> defs = classOrInterfaceBody(name, false);
        //将类中的所有成员信息整合为一棵JCClassDecl语法树
        JCClassDecl result = toP(F.at(pos).ClassDef(
            mods, name, typarams, extending, implementing, defs));
        attach(result, dc);
        return result;
    }

上述代码中,classBody的解析由 classOrInterfaceBody()方法负责, 类成员信息全部解析成功后所有的类成员信息在List集合中,并由语法解析器的ClassDef()方法将其整合为一棵JCClassDecl

ClassDef()方法如下

public JCClassDecl ClassDef(JCModifiers mods, Name name,
                                List<JCTypeParameter> typarams, JCExpression extending,
                                List<JCExpression> implementing, List<JCTree> defs) {
				// 解析 JCClassDecl语法树
  			JCClassDecl tree = new JCClassDecl(mods, name, typarams, extending, implementing, defs, null);
        tree.pos = pos;
        return tree;
    }

当成功将classBody中的内容信息解析并整合为一棵JCClassDecl语法树后,parseCompilationUnit()方法就会调用语法解析器的TopLevel()方法将之前解析过的package语法节点、import语法树和class语法树等内容信息全部整合为一棵JCCompilationUnit语法树。代码如下:

public JCCompilationUnit TopLevel(List<JCTree> defs) {
        for (JCTree node : defs)
            Assert.check(node instanceof JCClassDecl
                || node instanceof JCPackageDecl
                || node instanceof JCImport
                || node instanceof JCModuleDecl
                || node instanceof JCSkip
                || node instanceof JCErroneous
                || (node instanceof JCExpressionStatement
                    && ((JCExpressionStatement)node).expr instanceof JCErroneous),
                    () -> node.getClass().getSimpleName());
        JCCompilationUnit tree = new JCCompilationUnit(defs);
        tree.pos = pos;
        return tree;
    }

第三步:语义解析

1、主要步骤

语法树能表示一个结构正确的源程序的抽象,但无法保证源程序是符合逻辑的。而语义分析的主要任务是读结构上正确的源程序进行上下文有关性质的审查。

语义分析过程分为标注检查和数据及控制流分析两个步骤:

  • 为没有构造方法的类型添加缺省的无参构造方法;
  • 检查任何类型的变量在使用前是否都已经经历过初始化;
  • 检查变量类型是否与值匹配;
  • 将String类型的常量进行合并处理;
  • 检查代码中的所有操作语句是否可达;
  • 异常检查;
  • 解除Java语法糖;
2、额外话题

这里有一个比较有趣的现象,那就是一个string类型的变量中如果包含多个常量信息并通过符号“+”组合在一起时,底层究竟创建了多少个string对象?

从表面看,相信大家都会误以为string的对象数量与常量数量是等价的,可是事实并非如此,编译器在执行语义解析时,会检查string变量中是否包含多个常量信息并通过符号“+”组合在一起,如果确实存在,语义解析器则会将其合并为一个字符串,这就是常量折叠操作。

如下所示:

// 语义解析前
String str = "名字:朱佳文" + ", 性别:男 ";
// 语义解析后
String str = 名字:朱佳文, 性别:男 ";

当经历过这一系列语义解析步骤之后,就构成了一个完善的编译前提,编译器将会使用这个扩充后的语法树将其编译为Java字节码

第四步:字节码生成

成功经历过词法分析、语法分析和语义分析等步骤后,所解析出来的语法树已经非常完善了,那么javac编译器最后的任务就是调用com.sun.tools.javac.jvm.Gen类将这棵语法树编译为Java字节码文件,所谓编译字节码就是把符合Java语法规范的Java代码转换为符合JVM规范的字节码文件。

ps:JVM的架构模型是基于栈的,也就是说,在JVM中所有的操作都需要经过入栈和出栈来完成。

字节码生成是Javac编译过程的最后一个阶段。字节码生成阶段不仅仅是把前面各个步骤所生成的信息转化成字节码写到磁盘中,编译器还进行了少量的代码添加和转换工作。 实例构造器()方法和类构造器()方法就是在这个阶段添加到语法树之中的(这里的实例构造器并不是指默认的构造函数,而是指我们自己重载的构造函数,如果用户代码中没有提供任何构造函数,那编译器会自动添加一个没有参数、访问权限与当前类一致的默认构造函数,这个工作在填充符号表阶段就已经完成了)。

标签:Java,字节,编译器,pos,语法,编译,token,JVM,解析
From: https://blog.51cto.com/u_11906056/7060959

相关文章

  • 【我和openGauss的故事】构建openGauss开发编译提交一体化环境
    大数据模型[openGauss](javascript:void(0);)2023-07-2917:58发表于四川前文本文适合对openGauss源代码有好奇心的爱好者,那么一个友好的openGauss源代码环境应该是怎么样的。openGauss的开发环境是如何设置的?openGauss的编译环境是如何构建的?如何向openGauss提交代码,笔者集合官......
  • JVM之内存结构
    从整体上看JVM的内存分为两大类:线程私有的和线程共享的。线程私有:程序计数器虚拟机栈本地方法栈线程共享:堆区方法区程序计数器主要作用就是记住下一条JVM指令的执行地址。因为在多线程的情况下,同一个时间单核CPU只会执行一个线程中的方法,也就是说CPU会不断切换执行的......
  • VS2019编译CloudCompare2.12.4
    参考:https://blog.csdn.net/pingfanderen5/article/details/1261800821.VisualStudio2019对应v142工具2.安装QT,qt5.14.2及以前的版本存在下载包,下载地址:http://download.qt.io/ ,但是5.14.2只能支持到VS2017。 所以采用在线安装的方式安装qt5.15.2版本 源码准......
  • 1、编译 glibc 过程中报错 ../configure --prefix=/opt/glibc-2.27       2、首
    64位安装包,查看系统位数,安装对应的mysqlLinux系统安装MySQL时,将MySQL-5.6.17-1.el6.x86_64.rpm-bundle.tar包打开,有7个rpm文件,如下:MySQL-client-5.6.17-1.el6.x86_64.rpmMySQL-devel-5.6.17-1.el6.x86_64.rpmMySQL-embedded-5.6.17-1.el6.x86_64.rpmMySQL-server-5.6.17-1.el6.......
  • openssl安装编译
    Ubuntuopenssl安装编译编译cmake时报错缺少openssl依赖[missing:OPENSSL_CRYPTO_LIBRARY]CMakeErrorat/usr/share/cmake-3.10/Modules/FindPackageHandleStandardArgs.cmake:137(message):CouldNOTfindOpenSSL,trytosetthepathtoOpenSSLrootfolderinthe......
  • 编译安装最新版本VIM
    编译安装最新版本VIM命令:gitclonehttps://github.com/vim/vim.gitcdvim/srcmakesudomakeinstallvim编译时可能出现的错误1.错误:noacceptableCcompilerfoundin$PATH解决办法:安装GCC软件套件atpinstallgcc2.编译vim时Youneedtoinstallaterminallib......
  • JVM垃圾收集器
    一、垃圾回收器总览垃圾收集可以划分为几个阶段。。第一阶段:单线程收集时代(Serial和SerialOld)第二阶段:多线程收集时代(ParallelScanvenge和ParallelOld)第三阶段:并发收集时代(ParNew和CMS)第四阶段:智能并发收集时代(G1)下面的图一方面介绍了有哪些垃圾收集器,另外一方面也描述了每个垃......
  • JVM性能监控和调优
    JVM性能监控和调优JVM(Java虚拟机)调优是为了优化Java应用程序的性能和稳定性。JVM调优的目的是通过调整JVM的配置参数和优化应用程序代码,使其在给定的硬件和软件环境下达到更好的性能表现。防止出现OOM,进行JVM规划和预调优,解决程序中出现的各种OOM,减少FullGC出现的频率,解决运行慢......
  • 如何在32位ubuntu11.10 下编译android 4.0.1源码和goldfish内核
    一准备工作 1安装javasdk6(1)从jdk官方网站http://www.oracle.com/technetwork/java/javase/downloads/jdk-6u29-download-513648.html下载jdk-6u29-linux-i586.bin文件。(2)执行jdk安装文件 [html] viewplaincopy1.$chmoda+xjdk-6u29-linux-i586.bin2.$jdk......
  • JAVA 内存详解 (理解 JVM 如何使用 Windows 和 Linux 上的本机内存)
    级别:中级AndrewHall ,软件工程师,IBM2009年5月11日Java™堆耗尽并不是造成 java.lang.OutOfMemoryError 的惟一原因。如果本机内存 耗尽,则会发生普通调试技巧无法解决的 OutOfMemoryError 。本文将讨论本机内存的概念,Java运行时如何使用它,它被耗......