JVM属于JRE的一部分
JVM屏蔽了底层系统的差异
JVM分为两个版本,Client VM和Server VM,但JDK8以后基本没有Client VM了,通过命令java -version查看 JRE是JDK的一部分
JRE包含了java程序运行时所需要的底层的类库,大部分是用C和C++语言去写的
JDK除了包含JRE以外,还包含了编译Java代码所需要的编译器、监控JVM的一些监控工具等等
从上图中可以看出:JVM的作用就是屏蔽底层硬件、指令层面的细节,让java程序实现跨平台运行
跨平台分为两种
编译器层面跨平台:C/C++。底层系统层面的调用,不同的系统要用不同的语句 软件层面跨平台:Java。屏蔽底层硬件、指令层面的细节。类似适配器JVM会屏蔽系统底层硬件与指令的差异,生成对应系统的机器码,操作系统最底层真正执行的代码是机器码01010101。注意:不同的操作系统,指令码不一样,所以会有对应版本的JDK中JVM的实现
JVM主要由三大部分组成:类装载子系统、运行时数据区、执行引擎
底层操作系统是数据流、指令流、控制流,所以将运行时数据区分为两大类:数据、指令 运行时数据区分为五部分:程序计数器、本地方法栈、虚拟机栈、堆、方法区 线程安全问题 JDK1.8之前和JDK1.8后的版本运行时数据区的变化 元空间是直接的物理内存,可以自动扩容。但是如果无线扩容下去就会挤压其他内存空间,所以需要定义MaxMetaSpaceSize(最大元空间大小)
-XX:MetaspaceSize,初始空间大小,达到该值就会触发垃圾收集进行类型卸载,同时GC会对该值进行调整:如果释放了大量的空间,就适当降低该值;如果释放了很少的空间,那么在不超过MaxMetaspaceSize时,适当提高该值。 -XX:MaxMetaspaceSize,最大空间,默认是没有限制的。 除了上面两个指定大小的选项以外,还有两个与 GC 相关的属性: -XX:MinMetaspaceFreeRatio,在GC之后,最小的Metaspace剩余空间容量的百分比,减少为分配空间所导致的垃圾收集 -XX:MaxMetaspaceFreeRatio,在GC之后,最大的Metaspace剩余空间容量的百分比,减少为释放空间所导致的垃圾收集 -verbose参数是为了获取类型加载和卸载的信息调优:
Xms 是指设定程序启动时占用内存大小。一般来讲,大点,程序会启动的快一点,但是也可能会导致机器暂时间变慢。 Xmx 是指设定程序运行期间最大可占用的内存大小。如果程序运行需要占用更多的内存,超出了这个设置值,就会抛出OutOfMemory异常。 Xss 是指设定每个线程的堆栈大小 以上三个参数的设置都是默认以Byte为单位的,也可以在数字后面添加[k/K]或者[m/M]来表示KB或者MB。而且,超过机器本身的内存大小也是不可以的,否则就等着机器变慢而不是程序变慢了。 -Xms 为jvm启动时分配的内存,比如-Xms200m,表示分配200M -Xmx 为jvm运行过程中分配的最大内存,比如-Xms500m,表示jvm进程最多只能够占用500M内存 -Xss 为jvm启动的每个线程分配的内存大小,默认JDK1.4中是256K,JDK1.5+中是1M指向当前线程正在执行的字节码指令地址(行号)。线程私有,无GC(线程独享,每个线程都有自己的程序计数器)。
也叫线程栈。 虚拟机栈—>栈—>数据结构—>存储数据 存储 当前线程 运行方法时 所需要的数据、指令、返回地址。线程私有,无GC 特点:FILO,先进后出。 内部包含栈帧,每个线程执行每个方法的时候都会在栈中申请一个栈帧。一个方法一个栈帧 线程是用来执行方法的,至于怎么执行,取决于虚拟机栈
栈帧:一个方法对应一块栈帧内存区域。
栈帧中包含:局部变量表、操作数栈、动态链接、方法出口等等
局部变量表:存放程序运行时局部变量的表。是一块内存区域
操作数栈:程序在运行时,对数据进行临时操作的内存区域。数据结构也是栈。是一块内存区域
方法出口:方法被调用的位置(方法调用方调用方法结束后,方法调用方程序计数器的值)。是一块内存区域
动态链接:JVM在执行程序时,会将静态符号解析成符号所对应的直接引用。是一块内存区域
A a= new A(); int b = a.fun(); 前言: 类加载进方法区内存时,会有相应的类元信息(包含指令码); 在new对象时(堆内存),会在对象头里面存放一个对象所属类的类元信息地址(这样就可以知道这个对象是属于哪个类,该指针指向方法区内存中对应类的类元信息--类型指针) 动态链接: 方法在JVM内存中都是以静态符号的形式存在于常量池 存放着静态符号对应的JVM指令码在内存中入口的位置(首地址)(程序在运行到a.fun()时才知道这个位置的) 怎么知道的?因为在new对象的时候,会在对象的对象头中存放一个对象所属类的类型指针,当执行到对象的某个方法时,就会根据这个类型指针动态的找到这个方法所对应的类元信息的指令码,存放到动态链接中。(前提:在程序运行过程中),在程序为运行到此处时,动态链接中保存的是静态符号 JVM执行到含有动态链接的指令码(调用方法时)-->通过静态符号(在常量池中)(两部分组成:类和成员:类.成员)-->JVM执行到静态符号时,会解析静态符号,将静态符号转换成其对应的直接引用(对应的指令码)。这些指令码实际也是静态的,但是一旦运行,就会将这些指令码装载到方法区内存区域,此时该指令码就会有一个入口的内存地址,类型指针会通过静态符号找到该静态符号所拥有的JVM内存地址指令码内存地址指针,然后把内存地址指针存到动态链接中整个过程程序计数器会参与
1、将某种类型的的常量X压入操作数栈 2、将某种类型值存储到局部变量表n(其中n代表位置,局部变量表0放的是this,局部变量表会开辟一块内存空间来存储某个变量)。 3、如果需要用到存储到局部变量表n中的值参与运算,则从局部变量表n中装载某种类型值到操作数栈 4、执行某种类型的运算操作 5、运算完成后将结果放入操作数栈,然后再存储到局部变量表n中 6、如果需要用到结果返回,则需从局部变量表n中装载某种类型值到操作数栈,然后再返回到方法调用方的栈帧中的操作数栈中,再存储到方法调用方栈中局部变量表里举例:
1、类A
public class A { public static final int CONTANT = 666; public int fun(){ int a = 2; int b = 5; int c = a*b; return c; } public static void main(String[] args){ A aa= new A(); int bb = aa.fun(); System.out.println(bb); } }2、反编译字节码文件A.class中fun方法:
生成指令码命令:javap -c 字节码文件 > test.txt
生成更多指令码命令:javap -v 字节码文件 > test.txt 动态链接可以使用此命令查看反编译文件理解
命令:javap -c A.class > a.txt 或 javap -v A.class > a.txt
public int fun(); Code: 0: iconst_2 //将int类型的的常量2压入操作数栈 1: istore_1 //将int类型值存储到局部变量表1 2: iconst_5 //将int类型的的常量5压入操作数栈 3: istore_2 //将int类型值存储到局部变量表2 4: iload_1 //从局部变量表1中装载int类型值到操作数栈 5: iload_2 //从局部变量表2中装载int类型值到操作数栈 6: imul //执行int类型的乘法 7: istore_3 //将int类型值存储到局部变量表3 8: iload_3 //从局部变量表3中装载int类型值到操作数栈 9: ireturn //从方法中返回int类型的数据3、图例说明:
存储类实例,一个JVM实例只有一个堆内存,线程共享,要GC 可通过:jvisualvm命令启动JVM工具查看内存使用情况
堆内存分配原则:
1、优先分配Eden区 2、大对象直接分配到老年代 3、长期存活的对象分配到老年代 4、空间分配担保 检查老年代最大可用的连续空间是否大于历次晋升到老年代对象的平均大小 5、动态对象年龄对象 如果在Survivor空间中相同年龄所有对象大小的总和大于Survivor空间的一半,年龄大于或等于该年龄的对象可以直接进入老年代存储当前线程运行本地方法所需要的数据、指令、返回地址。线程私有,无GC
每一个线程都可能调用本地方法
native修饰的方法,底层不是Java实现,而是C/C++实现
非Java语言实现的方法在执行的过程中,也有内部的变量需要分配内存存放,所以就是用到本地方法栈内存区域
用于支持native方法的执行,存储了每个native方法调用的状态。
存放了要加载的类信息(字段方法的的字节码、部分方法的构造器,也称类元信息)常量、运行时常量池(JDK1.7+有变化)、静态变量、JIT(即时编译的信息,JDK1.7以前)。线程共享,无GC,非堆区(non-heap)
方法区是一种定义、概念,而所谓的永久代或元空间是其一种实现机制。
在JDK1.8之前方法区代表的是永久代(PermanetGeneration),JDK1.8及以后用元空间(Meta Space)代替。元空间是直接内存
JDK1.6及之前:有永久代,常量池在方法区 JDK1.7:有永久代,但已逐步去“永久代”,常量池在堆 JDK1.8之后:无永久代,常量池在元空间