Cover image courtesy of divinetechygirl
在使用JVM生态系统时,花一些时间并了解幕后发生的事情很重要。 即使在基本级别上,我们也应该能够用简单的词来理解和解释什么是JVM,编译如何工作,什么是字节码以及如何读取。
在本教程中,我们将看到10000英尺的JVM视图,了解一些基本概念,并学习如何从一个简单的程序中读取字节码。
开始吧。
What is the JVM?
简单来说,JVM是一种引擎,它以Java虚拟机规范中指定的格式读取已编译的代码,并在当前计算机上执行该代码。 这种方法的优点主要是跨平台兼容性,因为应该将编译后的代码(称为字节码)与平台无关。
这意味着在Linux机器中编译的代码和在Windows机器中编译的代码都应该在JVM中工作。 我们可以复制编译的。类从linux到Windows的文件,并在那里运行它们,没有问题,反之亦然。
换句话说,当您在Windows PC上安装Java时,爪哇该工具将使用特定于平台的运行时和JIT编译器在Windows上运行您的代码。 的爪哇c另一方面会编译您的.爪哇文件转换为通用字节码格式。
The Bytecode itself is a format that follows a specification from the Java Virtual Machine Specification. It has various features enabled based on the current version. Those features are dictated based on JSR's or Java Specification Requests and based on the current implementation. Here is the list for the OpenJDK 9 for example:
102: Process API Updates
110: HTTP 2 Client
143: Improve Contended Locking
158: Unified JVM Logging
165: Compiler Control
193: Variable Handles
197: Segmented Code Cache
199: Smart Java Compilation, Phase Two
200: The Modular JDK
201: Modular Source Code
211: Elide Deprecation Warnings on Import Statements
212: Resolve Lint and Doclint Warnings
213: Milling Project Coin
214: Remove GC Combinations Deprecated in JDK 8
215: Tiered Attribution for javac
216: Process Import Statements Correctly
217: Annotations Pipeline 2.0
219: Datagram Transport Layer Security (DTLS)
220: Modular Run-Time Images
221: Simplified Doclet API
222: jshell: The Java Shell (Read-Eval-Print Loop)
223: New Version-String Scheme
224: HTML5 Javadoc
225: Javadoc Search
226: UTF-8 Property Files
227: Unicode 7.0
228: Add More Diagnostic Commands
229: Create PKCS12 Keystores by Default
231: Remove Launch-Time JRE Version Selection
232: Improve Secure Application Performance
233: Generate Run-Time Compiler Tests Automatically
235: Test Class-File Attributes Generated by javac
236: Parser API for Nashorn
237: Linux/AArch64 Port
238: Multi-Release JAR Files
240: Remove the JVM TI hprof Agent
241: Remove the jhat Tool
243: Java-Level JVM Compiler Interface
244: TLS Application-Layer Protocol Negotiation Extension
245: Validate JVM Command-Line Flag Arguments
246: Leverage CPU Instructions for GHASH and RSA
247: Compile for Older Platform Versions
248: Make G1 the Default Garbage Collector
249: OCSP Stapling for TLS
250: Store Interned Strings in CDS Archives
251: Multi-Resolution Images
252: Use CLDR Locale Data by Default
253: Prepare JavaFX UI Controls & CSS APIs for Modularization
254: Compact Strings
255: Merge Selected Xerces 2.11.0 Updates into JAXP
256: BeanInfo Annotations
257: Update JavaFX/Media to Newer Version of GStreamer
258: HarfBuzz Font-Layout Engine
259: Stack-Walking API
260: Encapsulate Most Internal APIs
261: Module System
262: TIFF Image I/O
263: HiDPI Graphics on Windows and Linux
264: Platform Logging API and Service
265: Marlin Graphics Renderer
266: More Concurrency Updates
267: Unicode 8.0
268: XML Catalogs
269: Convenience Factory Methods for Collections
270: Reserved Stack Areas for Critical Sections
271: Unified GC Logging
272: Platform-Specific Desktop Features
273: DRBG-Based SecureRandom Implementations
274: Enhanced Method Handles
275: Modular Java Application Packaging
276: Dynamic Linking of Language-Defined Object Models
277: Enhanced Deprecation
278: Additional Tests for Humongous Objects in G1
279: Improve Test-Failure Troubleshooting
280: Indify String Concatenation
281: HotSpot C++ Unit-Test Framework
282: jlink: The Java Linker
283: Enable GTK 3 on Linux
284: New HotSpot Build System
285: Spin-Wait Hints
287: SHA-3 Hash Algorithms
288: Disable SHA-1 Certificates
289: Deprecate the Applet API
290: Filter Incoming Serialization Data
291: Deprecate the Concurrent Mark Sweep (CMS) Garbage Collector
292: Implement Selected ECMAScript 6 Features in Nashorn
294: Linux/s390x Port
295: Ahead-of-Time Compilation
297: Unified arm32/arm64 Port
298: Remove Demos and Samples
299: Reorganize Documentation
How to read Bytecode?
让我们更实际一些,尝试从一个简单的程序理解和读取字节码。
For the purposes of this tutorial I will be using IntelliJ IDEA with the ASM Bytecode Outline Plugin but you can also use VScode
with the javap
as well.
- 使用编辑器对话框创建一个新的Java程序。创建一个名为的新Java文件确定OS.java with the following code:
public class DetermineOS {
public static void main(String[] args) {
String strOSName = System.getProperty("os.name");
System.out.print("Display the current OS name example.. OS is ");
if(strOSName != null)
{
if(strOSName.toLowerCase().contains("linux"))
System.out.println("Linux");
else
System.out.print("not Linux");
}
}
}
上面的代码只是检索操作系统名称系统属性,并检查它是否包含字符串linux。 然后,根据这种情况,它会打印一些字符串。
![](/qrcode.jpg)
- 点击View -> Show Bytecode with Jclasslib
在右侧出现的面板中,我们可以看到有关字节码的一些信息。 让我们一一讲解:
- 一般信息:本节显示有关编译此类的JVM版本的特定信息,常量池中的常量数,此类的访问标志以及一些其他计数器:
- 恒定池:JVM为其在类路径中看到的每种类型加载常量的哈希图。 该映射基本上由文字值,字符串和其他对类型或字段的引用组成。 所有值均由唯一键引用。 在我们的示例中,我们可以看到操作系统名称装有钥匙#33:
- 接口+字段:此部分显示所有接口和字段声明。 由于我们未指定任何内容,因此该部分为空。方法:本节将包含字节码的内容。 目前,我们在程序中定义了一个主要方法。 什么时候Java语言编译此代码,它将创建两个条目,一个用于方法,一个用于主要方法。 让我们进一步探讨这些:: The bytecode that is emitted for this method is the following:
0 aload_0
1 invokespecial #1 <java/lang/Object.<init>>
4 return
在JVM中,类的每个构造函数(即使未定义)都作为对的调用而调用<init>由编译器提供。 以上说明是致电所需的最低要求<init>。aload_0 instructs the runtime to load the local reference at index 0 of the current frame。This contains a reference to <java/lang/Object。<init>>本身,所以下一条指令invokespecial#1 is a special instance call that calls referenced as #1。Then we 返回。
- 主要: The bytecode that is emitted for this method is the following:
0 ldc #5 <os.name>
2 invokestatic #6 <java/lang/System.getProperty>
5 astore_1
6 getstatic #2 <java/lang/System.out>
9 ldc #7 <Display the current OS name example.. OS is >
11 invokevirtual #4 <java/io/PrintStream.print>
14 aload_1
15 ifnull 49 (+34)
18 aload_1
19 invokevirtual #8 <java/lang/String.toLowerCase>
22 ldc #9 <linux>
24 invokevirtual #10 <java/lang/String.contains>
27 ifeq 41 (+14)
30 getstatic #2 <java/lang/System.out>
33 ldc #11 <Linux>
35 invokevirtual #12 <java/io/PrintStream.println>
38 goto 49 (+11)
41 getstatic #2 <java/lang/System.out>
44 ldc #13 <not Linux>
46 invokevirtual #4 <java/io/PrintStream.print>
49 return
这比较复杂,但是如果您仔细研究一下,可以将其与代码本身进行映射。 字节码面板通过为每个指令提供链接来帮助我们。 以下是其中一些解释:
0 ldc #5
从“ os.name”常量池中将项目#5推入堆栈
2 invokestatic #6
调用在#6处引用的静态方法<java/lang/System.getProperty>。 这将消耗我们之前传递的顶部堆栈框架变量。
5 astore_1
从顶部堆栈位置将引用存储到索引为1的本地帧中。这基本上是上次调用invokestatic的结果。 至此,我们完成了以下操作:
字符串strOSName = System.getProperty(“ os.name”);
15 ifnull 49
有条件分支的意思。 如果当前堆栈顶部的值由上一条指令获取aload_1这是我们用来存储结果的那个astore_1为null,然后转到第49行,否则继续到下一行。 我们的字节码中的第49行是:
49 return
您可以继续阅读字节码并了解什么虚拟调用,静态的要么ifeq意思。
- 属性:有关实际源文件的信息在此处显示。 例如,我们可以看到指向常量池映射的文件名:
注意:如果要使用javap工具查看代码,可以发出以下命令:
$ javap -c out.production.jvm-experiments.DetermineOS
我已经命名了我的项目jvm-实验因此您需要使用项目名称输入正确的路径。
More information
现在就这样。 我希望您喜欢我们今天所做的事情,并了解JVM和Bytecode的基本块。 如果您想了解有关JVM生态系统的更多信息,我可以推荐以下资源:
- JVM Specification: This is the defacto reference for everything it has to do with the JVM platform.
- Jprofiler: Profiling your Java applications like a boss.
- Tutorials Point: Quick and easy tutorials
from: https://dev.to//theodesp/how-to-read-java-bytecode-with-examples-4p7g