Java代码的一生大致如下图所示,其中,JVM运行时数据区是我们需要重点关注的部分:
方法区:存储JVM加载的类信息、常量、静态变量、编译后的代码等数据。在虚拟机规范中,对于方法区的定义是一个不严谨的、逻辑上的概念,实际上各虚拟机会有不同的实现。
堆内存:JVM启动时创建该区域,主要用于存储对象的实例,GC垃圾回收主要是处理堆内存。
栈内存:或称为虚拟机栈,由多个栈帧组成,一个JVM线程会执行一个或多个方法,而__一个方法对应一个栈帧__。一个栈帧包括:局部变量表、操作数栈等部分。每个线程的栈内存大小默认是1M,超出则报StackOverflowError。
本地方法栈:顾名思义,是为JVM执行native方法准备的。
程序计数器:由于CPU在单位轮片时间内,只执行一个线程的指令。每个线程的程序计数器记录当前线程执行的字节码位置,存储的是字节码的地址(如果执行的native方法,则计数器数值为空)。这样在线程切换出去再切换回来时,就能找到上次执行的指令位置,然后继续执行。
public class Demo01 { public static void main(String[] args) { int a = 200; int b = 100; int c = a / b; int d = 3; System.err.println(c + d); } } 复制代码
2.1.2 编译(javac)并解析(javap) Demo.class
# 编译 javac Demo01.java # 解析。并将指令内容输出到Demo01.txt中 javap -v Demo01.class > Demo01.txt 复制代码
解析后的指令内容如下:
Classfile /Users/zephyrlai/IdeaProjects/zephyr/java-arch/src/cn/zephyr/ch1/Demo01.class Last modified 2020-3-10; size 429 bytes MD5 checksum 5c8846f77018cc43a36eeba64ff68cbd Compiled from "Demo01.java" public class cn.zephyr.ch1.Demo01 minor version: 0 major version: 52 flags: ACC_PUBLIC, ACC_SUPER Constant pool: #1 = Methodref #5.#14 // java/lang/Object."<init>":()V #2 = Fieldref #15.#16 // java/lang/System.err:Ljava/io/PrintStream; #3 = Methodref #17.#18 // java/io/PrintStream.println:(I)V #4 = Class #19 // cn/zephyr/ch1/Demo01 #5 = Class #20 // java/lang/Object #6 = Utf8 <init> #7 = Utf8 ()V #8 = Utf8 Code #9 = Utf8 LineNumberTable #10 = Utf8 main #11 = Utf8 ([Ljava/lang/String;)V #12 = Utf8 SourceFile #13 = Utf8 Demo01.java #14 = NameAndType #6:#7 // "<init>":()V #15 = Class #21 // java/lang/System #16 = NameAndType #22:#23 // err:Ljava/io/PrintStream; #17 = Class #24 // java/io/PrintStream #18 = NameAndType #25:#26 // println:(I)V #19 = Utf8 cn/zephyr/ch1/Demo01 #20 = Utf8 java/lang/Object #21 = Utf8 java/lang/System #22 = Utf8 err #23 = Utf8 Ljava/io/PrintStream; #24 = Utf8 java/io/PrintStream #25 = Utf8 println #26 = Utf8 (I)V { public cn.zephyr.ch1.Demo01(); descriptor: ()V flags: ACC_PUBLIC Code: stack=1, locals=1, args_size=1 0: aload_0 1: invokespecial #1 // Method java/lang/Object."<init>":()V 4: return LineNumberTable: line 9: 0 public static void main(java.lang.String[]); descriptor: ([Ljava/lang/String;)V flags: ACC_PUBLIC, ACC_STATIC Code: stack=3, locals=5, args_size=1 0: bipush 200 2: istore_1 3: sipush 100 6: istore_2 7: iload_1 8: iload_2 9: idiv 10: istore_3 11: iconst_3 12: istore 4 14: getstatic #2 // Field java/lang/System.err:Ljava/io/PrintStream; 17: iload_3 18: iload 4 20: iadd 21: invokevirtual #3 // Method java/io/PrintStream.println:(I)V 24: return LineNumberTable: line 11: 0 line 12: 3 line 13: 7 line 14: 11 line 15: 14 line 16: 24 } SourceFile: "Demo01.java" 复制代码
可以看到大致可以分为如下4块
前4行:无关代码内容的描述信息
5~8行:版本号、访问标志
(major-verion)版本号:jdk5、6、7、8分别是49、50、51、52
访问标志:
标志名称 | 标志值 | 含义 |
---|---|---|
ACC_PUBLIC | 0x0001 | 标记为public类型 |
ACC_FINAL | 0x0010 | 标记被声明为final(仅类能设置) |
ACC_SUPER | 0x0020 | jdk1.2之后为true(是否使用invokespecial字节码指令) |
ACC_INTERFACE | 0x0200 | 标记为接口 |
ACC_ABSTRACT | 0x0400 | 标记为abstract类型(对于接口、抽象类则为true) |
ACC_SYNTHETIC | 0x1000 | 标记这个类不是用户产生的 |
ACC_ANNOTATION | 0x2000 | 标记为注解类 |
ACC_ENUM | 0x4000 | 标记为枚举类 |
以Constant pool为起点的常量池(区别于String常量池):主要存储类信息中包含的静态常量,即编译当前类之后,所用到的常量。例如类名、方法名等。常见的常量类型如下:
以 {}
包裹的业务代码,而这块业务代码包含了2块:
stack=3, locals=5, args_size=1
主要加载各种类信息、以及类中用到的常量、静态变量等,存储在方法区。创建类的实例,存储在堆内存中。