定义:
Java Visual Machine(JVM),Java程序的运行环境,这里指Java二进制字节码的运行环境。
特点:
一次编写,跨平台运行。JVM屏蔽了字节码跟底层操作系统的差异,对外提供了一致的运行环境。JVM解释了字节码的流程,实现了跨平台机制。
自动内存管理,垃圾回收功能。
数组下标越界检查。Java程序遇到下标越界会直接抛异常,但是不会去覆盖越界的那块内存的数据。相较于C/C++语言(会覆盖掉那块内存的值),可以说抛异常是更友好的一种处理机制。
JVM,JRE、JDK的区别

从这个图中可以看出 他们之间是一种逐层向上,互相包含的关系。上一层是下一层的基础上的进一步补充。
JVM结构

1、程序计数器(Program Counter Register)
概念:当Java文件编译成二进制字节码文件后,一行行代码变成了一行行JVM指令,此时的指令还不能直接交给CPU去执行,需要通过解释器翻译成一条条机器码,再交给CPU去执行。程序计算器的作用是在当前指令运行时,记住下一条指令运行的地址。当指令被加载进内存当中时,每条指令的前面都会带有一个地址信息。根据这个地址信息,就能找到是哪条指令去执行它。在物理上,是通过寄存器去实现。寄存器可以说在整个CPU组件里读写速度最快的一个单元。
特点:线程私有。当多线程并发时,CPU的调度器会为每个线程分配一个时间片,比如说,线程1在时间片内代码没有执行完,会先暂停,然后切换到线程2去运行。当从线程2又切回到线程1时,这个时候要知道该从那里继续往下执行,那这也需要用到程序计数器。线程1的计数器记录的是线程1中的下条指令,跟线程2是独立开的。所以说是线程私有的。
不会内存溢出。因为计数器记录的永远是下一条指令的地址,所以不存在内存溢出的情况。
2、JVM 栈(stack)
概念:JVM的每个线程运行时,都需要一块内存空间。虚拟机栈是每个线程运行需要的内存空间。
一个栈内是由多个栈帧(Frame)组成,栈帧是方法时运行所需要的内存,像方法内的变量、参数、返回值都是会占用内存空间,因此事先就得把这些地址空间给预留出来。
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
method1();
}
public void method1(){
int a=10;
method2(a);
}
private int method2(int a) {
int b=20;
int sum=a+b;
return sum;
}
}

上述代码我们用debug跟到method2方法内时,我们发现当前栈内存在着oncreate、method1、method2,根据先进后出的原则,最后调用的method2方法位于栈顶(栈内还有其它许多栈帧,是由于Android程序在执行到oncreate方法之时,已经执行了一系列其它方法,这里暂不展开讨论,了解即可)。
特点:
1、不涉及到垃圾回收。栈内的方法,在执行结束后,会被弹出栈,也就是方法结束时,会自动进行内存回收。
栈内内存分配不是越大越大。栈内存分配的大 只是说可以存放的栈帧会更多,递归时可以递归更多次。但栈内存分配多了,必然会导致其它内存分配得少了,会导致线程数量变少,反而会运行更慢。
2、方法内的局部变量是否线程安全?
- 如果方法内的变量,没有逃离方法的作用范围,那它是线程安全的
- 如果是局部变量引用的对象,并且逃离了方法的作用范围(比如参数和返回值对象,可能会被其它线程所访问到),需要考虑到线程安全。
栈内存溢出
- 栈内栈帧过多时,譬如递归没有设置结束时,栈内会存在大量的递归函数,导致溢出。
- 栈帧过大导致内存溢出。
3、本地方法栈(Native Method Stacks)
java在调用本地的一些方法时,需要给这些本地方法提供内存空间。
4、堆(Heap)
通过new关键字创建的对象,都会使用堆空间。
特点
- 它是线程共享的,堆中对象需要考虑线程安全的问题。
- 有垃圾回收机制
堆内存溢出
当堆内存中创建大量对象,且对象存在引用,无法被垃圾回收时,便可能会导致内存溢出。在开发时,需要注意,当某个对象不再使用的时候,要检查是否仍被其它对象持有引用。
5、方法区(Method area)
方法区主要存储被虚拟机加载的类的信息,常量、静态变量。
二进制字节码 class文件的格式
java文件在虚拟机上是以class文件存在的,它是二进制字节码。我们需要通过工具来看懂它。
//将java文件编译成class文件
D:\demo>javac Hello.java
//查看class文件的信息
D:\demo>javap -v Hello.class
public class Test {
public static void main(String[] args) {
System.out.println("Hello,world");
}
}
Classfile /D:/demo/Hello.class
------------------类的基本信息-----------------------------
Last modified 2021-10-3; size 415 bytes
MD5 checksum 09b1098c7b153143ee7f674157742226
Compiled from "Hello.java"
public class Hello
minor version: 0
major version: 52
flags: ACC_PUBLIC, ACC_SUPER
-------------------常量池-----------------------------------------
Constant pool:
#1 = Methodref #6.#15 // java/lang/Object."<init>":()V
#2 = Fieldref #16.#17 // java/lang/System.out:Ljava/io/PrintStream;
#3 = String #18 // Hello,world
#4 = Methodref #19.#20 // java/io/PrintStream.println:(Ljava/lang/String;)V
#5 = Class #21 // Hello
#6 = Class #22 // java/lang/Object
#7 = Utf8 <init>
#8 = Utf8 ()V
#9 = Utf8 Code
#10 = Utf8 LineNumberTable
#11 = Utf8 main
#12 = Utf8 ([Ljava/lang/String;)V
#13 = Utf8 SourceFile
#14 = Utf8 Hello.java
#15 = NameAndType #7:#8 // "<init>":()V
#16 = Class #23 // java/lang/System
#17 = NameAndType #24:#25 // out:Ljava/io/PrintStream;
#18 = Utf8 Hello,world
#19 = Class #26 // java/io/PrintStream
#20 = NameAndType #27:#28 // println:(Ljava/lang/String;)V
#21 = Utf8 Hello
#22 = Utf8 java/lang/Object
#23 = Utf8 java/lang/System
#24 = Utf8 out
#25 = Utf8 Ljava/io/PrintStream;
#26 = Utf8 java/io/PrintStream
#27 = Utf8 println
#28 = Utf8 (Ljava/lang/String;)V
------------------------方法定义---------------------------------
{
//构造方法
public Hello();
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 1: 0
//main方法
public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)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 #3 // String Hello,world
5: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
8: return
LineNumberTable:
line 3: 0
line 4: 8
}
SourceFile: "Hello.java"
解释器在解释虚拟机指令的时候,根据#后的数字,在常量池进行查表,比如#2对应常量池里的 #2 = Fieldref #16.#17 ; #2再从#16,#17中去找,直到最后找到需要执行的类名、方法名、参数信息,字节量等信息。
常量池本质上就是一张表,通过前面的地址,找到这些信息。
运行常量池:常量池是class文件中的,当class被加载的时候,它的常量池信息会被放入运行常量池信息中,#前面的地址也会变成真实地址信息。
网友评论