这篇文章以java为例讲解java源码到字节码到字节码对象再到jvm的内存中整个过程为例。从而来理解JVM的原理。JVM由ClassLoader,JVM运行时数据区,执行引擎三部分构成。这篇文章主要会讲解JVM运行时数据区的各个分区的内在特点以及各个分区之间的关系。
一,JVM简介
JVM(Java Virtual Machine)也就是java虚拟机。用来运行字节码码的。可以运行Java,kotlin,Scala,Clojure,Groovy,Jython,JRuby,Ceylon,Eta,Haxe 10余种语言编译的字节码。
后面就以Java来讲解。先给上一张图。可以看出JVM是在这个JDK中最底层的一小部分。但是他却起到到来了极其重要的作用。
java源码通过javac编译成.class的字节码文件然后在由java虚拟机运行。java具有垮平台的特点。它跨平台就是通过不同的操作系统上装上不同的JDK从而拥有不同的JVM来实现的跨平台。也就是一份编译出来的字节码文件可以在不同的操作系统的JVM上运行。
JVM由ClassLoader,JVM运行时数据区,执行引擎三部分构成。
下面是官方图:
下面来一个中文的简图,文章主要讲解运行时数据区的内存空间的分区,每个分区的内在特点,以及分区间的相互关系。
二,方法区
说到方法区就要提到 元空间,永久带 。方法区是规范,元空间,永久带是具体实现。
- 永久带是jdk8之前方法区的具体实现。他是放在堆里面的,所有他也会出现oom(out of memory),也会触发GC。
- 元空间是jdk8以后方法区的具体实现。他是放在直接内存中的。也就是操作系统内存。所有会出现native memory
1,元空间的默认大小是20.75M。最大内存就是系统内存。(目前系统64位,其中16位为保留位,最大内存就为2的48次方=256T)
2,元空间调优
(1)元空间最大最小设置成一样。这样可以防止内存抖动。
(2)元空间设置为多物理内存的1/32。具体多少需要进行调试。
(3)设置元空间的时候比起实际使用大小预留20%到30%这样比较安全。
方法区里面存放了:常量,静态变量,类信息。
三,程序计数器
程序计数器是指向当前线程执行的字节码指令的(地址)行号。
在cmd窗口执行命令 javap -c Test.class >Test.txt
打开Test.txt文件。程序计数器就是存放这个行号的。
Compiled from "Test.java"
public class AAA.Test {
public AAA.Test();
Code:
0: aload_0
1: invokespecial #12 // Method java/lang/Object."<init>":()V
4: aload_0
5: new #3 // class java/lang/Object
8: dup
9: invokespecial #12 // Method java/lang/Object."<init>":()V
12: putfield #14 // Field object:Ljava/lang/Object;
15: return
public int add();
Code:
0: iconst_1
1: istore_1
2: iconst_2
3: istore_2
4: iload_1
5: iload_2
6: iadd
7: bipush 100
9: imul
10: istore_3
11: iload_3
12: ireturn
public static void main(java.lang.String[]);
Code:
0: new #1 // class AAA/Test
3: dup
4: invokespecial #28 // Method "<init>":()V
7: astore_1
8: aload_1
9: invokevirtual #29 // Method add:()I
12: istore_2
13: getstatic #31 // Field java/lang/System.out:Ljava/io/PrintStream;
16: iload_2
17: invokevirtual #37 // Method java/io/PrintStream.println:(I)V
20: return
}
四,虚拟机栈(线程栈)
虚拟机栈里面存放的是栈帧,一个方法对应一个栈帧。
栈帧中存放了:局部变量表,操作数栈,动态链接,方法出口。
- 局部变量表:就是这个方法类的局部变量汇总。
- 操作数栈:这些局部变量在赋值给变量引用之前要先压入操作数栈。以及进行计算的时候临时存放变量的栈。
- 动态链接:就是存放这个方法在方法区内的内存地址。
- 方法出口:就是返回现场。比如在执行到main方法的第9行指令开始调用add()方法,这个时候就会把这个行号存到方法出口中,当add()方法执行结束然后就回到main()方法中执行第10行指令。
一个方法执行完JVM需要做的事情。
- 1,恢复局部变量表指针
- 2,恢复操作数栈指针
- 3,恢复程序技术器。
- 4,如果方法有返回地址,需要返回。
- 5,清理栈帧。(程序计数器去完成的)
这个是上面图对应的java代码
public class Test {
public int add() {
int a = 1;//局部变量
int b = 2;//局部变量
int c = (a+b)*100;
return c;
}
public static void main(String[] args) {
Test text=new Test();
int result = text.add();
System.out.println(result);
}
}
五,堆
1,堆是用来存放对象的地方
2,堆的默认大小
- 最小是是物理内存的1/64
- 最大是1/4
3,堆内存分区
- yang(年轻代)和old(老年代)两部分。默认比例为1:2
- yang(年轻代)又分为Eden(伊甸园区)和survivor0区,survivor1区。默认比例为8:1:1
六,本地方法栈
本地方法栈(Native Method Stacks)与虚拟机栈所发挥的作用是非常相似的,其区别不过是虚拟机栈为虚拟机执行Java方法(也就是字节码)服务,而本地方法栈则是为虚拟机使用到的Native方法服务。虚拟机规范中对本地方法栈中的方法使用的语言、使用方式与数据结构并没有强制规定,因此具体的虚拟机可以自由实现它。甚至有的虚拟机(譬如Sun HotSpot虚拟机)直接就把本地方法栈和虚拟机栈合二为一。与虚拟机栈一样,本地方法栈区域也会抛出StackOverflowError和OutOfMemoryError异常。
七,个内存区之间的关系
虚拟机栈-->方法区 动态链接。(虚拟机栈中的栈帧对应的方法在方法区的内存地址)
堆-->方法区 关联是class Pointer(也就是对象执行所对应的类的Class对象所属地址)
方法区-->堆 类里面的静态对象引用。(例如类里面的public static Test test = new Test();)