JVM
JVM是Java Virtual Machine (Java虚拟机)的缩写,是一种用于计算设备的规范
跨平台
编写Java代码后,编译后会生成.class
文件,俗称字节码,而JVM虚拟机就是负责将这些字节码文件翻译成特定平台的机器码后运行,也是说在不同的平台(wind、mac、Linux...)安装不同的JVM虚拟机,那么就可以运行.class
,而目前能编译成.class
语言也有很多种,如下:
优点
- 一套指定,多端运行
- 使用者多,框架多
- 成熟的GC回收机制
- 可移植性
缺点
- JVM启动太重
- 性能会有局面性
运行时流程图
- 一套字节码指令集class文件
- 类加载器
- 方法区
- 堆
- Java栈
- 本地方法栈
- 程序计数器
- 执行引擎
- 本地方法接口
- 本地库
- 绿色【方法区、堆】:运行时数区所在的线程共享
- 黄色【Java栈、本地方法栈、程序计数器】:运行时数区线程私有
类加载器:在以前文章 插件化开发 -- 类加载 有介绍过
运行时数区也是本文的重点
运行时数据区域(重点)
定义
当程序Run跑起来,必然会有一个线程(多数为主线程Main-Thread),Java虚拟机会把它所管理的 内存 划分成若干个不同的数据区域
类型
- 线程共享区
- 线程私有区
Java方法运行内存区域
有俩大点分别为:程序计数器,虚拟机栈
程序计数器
指向当前线程正在执行的字节码指定的地址,一般是对应当前操作数栈的顶端栈帧的位置,防止线程阻塞后被唤醒不知道之前执行的位置点。
虚拟机栈
Java虚拟机栈,除了Native方法需要在本地方法栈计算,Java的一般方法函数计算都是在虚拟机栈完成,具有重要性,具有存储当前线程运行方法,一个方法函数,理解为一个(main-栈帧),栈帧存储了方法局部变量表、操作数栈、动态连接和返回地址等信息。
每一个方法调用至执行完成的过程,都对应着一个栈帧在虚拟机里从入栈到出栈的过程
方法栈帧
-
局部变量表
方法内部的一些变量,参数值,以及一行代码“=”左边的结果集
-
操作数栈
常称为操作栈,数据结构是栈(Stack),具有先进后出(FILO)、后进先出(LIFO)特性,当一个方法刚刚执行,其操作数栈是空的,随着方法执行和字节指定,会从局部变量表或对象实例的字段中复制常量、变量写入到操作数栈,随着计算的进行将栈中元素出栈到局部变量或者是返回出口给调用者,会频繁的进/出栈操作。
-
动态连接
Class中一个方法需要调用其他方法,需要将这些方法的符号引用转化为其他内存地址的直接引用,而符号引用就存在方法区的运行时常量池,每个栈帧都有一个指向了运行时常量池中该栈所属的符号引用,这个引用的目的就是为了支持调用过程中的动态连接。
-
方法返回出口
- 正常完成出口
- 异常完成出口
分析操作数栈
一段代码:
public int add(int a, int b) {
int c = 0;
c = a + b;
return c;
}
public static void main(String[] args) {
Presenter presenter = new Presenter();
int outcome = presenter.add(10, 20);
}
通过 javac Presenter.java
编译成class文件,JVM可以编译的文件,
在通过 javap -c Presenter.class
JVM将class字节码进行反编译生成汇编代码
public int add(int, int);
Code:
0: iconst_0 //将int类型常量0压入栈, 计数器标记0位置
1: istore_3 //将int类型值存入局部变量3, 计数器标记1位置
2: iload_1 //从局部变量1中装载int类型值, 计数器标记2位置
3: iload_2 //从局部变量2中装载int类型值, 计数器标记3位置
4: iadd //执行int类型的加法, 计数器标记4位置
5: istore_3 //将int类型值存入局部变量3, 计数器标记5位置
6: iload_3 //从局部变量3中装载int类型值, 计数器标记6位置
7: ireturn //从方法中返回int类型的数据, 计数器标记7位置
public static void main(java.lang.String[]);
Code:
0: new #2 //创建一个新对象 #2动态连 // class com/neng/jvm/Presenter
3: dup //复制栈顶数值并将复制值压入栈顶
4: invokespecial #3 //调用需要特殊处理的实例方法 #3动态连 // Method "":()V
7: astore_1 //将引用类型或returnAddress类型值存入局部变量1
8: aload_1 //从局部变量1中装载引用类型值
9: bipush 10 //将一个10位带符号整数压入栈, 计数器会占用多个9的位置
11: bipush 20 //将一个20位带符号整数压入栈
13: invokevirtual #4 // Method add:(II)I
16: istore_2 //将int类型值存入局部变量2, 计数器标记为16
17: return //从方法中返回,返回值为void
通过指令表操作流程:
局部变量表,类似于数据表,自带物理位置,存储当前(函数)栈帧this
常量、计算结果,代码左边
操作数据栈,计算逻辑结果,即使一个常量变量也要先进入栈,再弹栈进入局部表,代码右边
计数器对操作数据栈的位置记录
《JVM 字节码指令集》 帮助查询:
变量到操作数栈:iload,iload_,lload,lload_,fload,fload_,dload,dload_,aload,aload_
操作数栈到变量:istore,istore_,lstore,lstore_,fstore,fstore_,dstore,dstor_,astore,astore_
常数到操作数栈:bipush,sipush,ldc,ldc_w,ldc2_w,aconst_null,iconst_ml,iconst_,lconst_,fconst_,dconst_
加:iadd,ladd,fadd,dadd
减:isub,lsub,fsub,dsub
乘:imul,lmul,fmul,dmul
除:idiv,ldiv,fdiv,ddiv
余数:irem,lrem,frem,drem
取负:ineg,lneg,fneg,dneg
移位:ishl,lshr,iushr,lshl,lshr,lushr
按位或:ior,lor
按位与:iand,land
按位异或:ixor,lxor
类型转换:i2l,i2f,i2d,l2f,l2d,f2d(放宽数值转换)
i2b,i2c,i2s,l2i,f2i,f2l,d2i,d2l,d2f(缩窄数值转换)
创建类实例:new
创建新数组:newarray,anewarray,multianwarray
访问类的域和类实例域:getfield,putfield,getstatic,putstatic
把数据装载到操作数栈:baload,caload,saload,iaload,laload,faload,daload,aaload
从操作数栈存存储到数组:bastore,castore,sastore,iastore,lastore,fastore,dastore,aastore
获取数组长度:arraylength
检相类实例或数组属性:instanceof,checkcast
操作数栈管理:pop,pop2,dup,dup2,dup_xl,dup2_xl,dup_x2,dup2_x2,swap
有条件转移:ifeq,iflt,ifle,ifne,ifgt,ifge,ifnull,ifnonnull,if_icmpeq,if_icmpene,
if_icmplt,if_icmpgt,if_icmple,if_icmpge,if_acmpeq,if_acmpne,lcmp,fcmpl
fcmpg,dcmpl,dcmpg
复合条件转移:tableswitch,lookupswitch
无条件转移:goto,goto_w,jsr,jsr_w,ret
调度对象的实例方法:invokevirtual
调用由接口实现的方法:invokeinterface
调用需要特殊处理的实例方法:invokespecial
调用命名类中的静态方法:invokestatic
方法返回:ireturn,lreturn,freturn,dreturn,areturn,return
异常:athrow
finally关键字的实现使用:jsr,jsr_w,ret
内存区域以及如何分配
JVM在内存处理的流程步骤:
- JVM申请内存
- 初始化运行时数据区
- 类加载
- 执行方法栈
- 创建对象
其中代码运行Run,不停的对4,5轮询的操作,进栈出栈,进局部变量表
代码对象如何在JVM分配呢?
本地方法栈:
- 存放各种Native方法的局部变量表之类的信息
- 和虚拟机栈类似,内部结构也是栈帧,每个Native方法执行创建一个栈帧
- 没有明确的规定内存大小
虚拟机栈
- 线程执行方法的时候内部存储的局部变量会从堆中对象的地址
- 线程是私有的,与线程同一时候创建,内部有局部变量表存储
- 栈大小如果是固定,压栈大于最大值,就抛出 StackOverflowError
- 栈大小如果是动态扩展,当超过内存空间支持,则抛出 OoutofMemoryError
程序计数器
- 记录当前执行线程执行到哪一条字节码指定(位置标记)
方法区
类型信息,类加载信息,被虚拟机加载的类元数据信息
常量、静态变量、以及编译器编译后的代码(运行常量池,例如【String】 “Hello Word” )
对象/数组地址指向堆
堆区
存放Java对象和数组,(字符串常量池-jdk8)
虚拟机中内存存储空间比较大的区域
可能出现OOM异常区域
-
该区域是GC的主要区域,堆区是有 年轻代 和 老年代 组成,频繁被GC回收,常量对象未被回收,就会由新转换老
JVM堆区的参数设置
# 设置堆区的初始大小 -Xms1024m # 设置堆区的存储空间最大值,一般与堆区的初始大小相等 -Xmx1024m # 设置年轻代堆的大小 -Xmn512m # 设置如下参数,在出现OOM时进行堆转储 -XX:+HeapDumpOnOutOfMemoryError # 设置以上设置时,需配置以下参数,堆转储文件输出的位置 -XX:HeapDumpPath=/usr/log/java_dump.hprof
参考:
《JVM 字节码指令集》
ava虚拟机—栈帧、操作数栈和局部变量表
从 Java 代码如何运行聊到 JVM 和对象的创建-分配-定位-布局-垃圾回收