java虚拟机
编码到执行大致流程图:
java代码跨平台运行
1.不同平台有对应的JVM,JVM从软件层面屏蔽不同的操作系统在底层硬件与指令的区别。
// JVM根据将字节码文件转换成平台可以识别的语言后则同一代码可在不同平台运行
jvm的组成
1.类装载子系统
1.1用于将class文件加载,连接,初始化的过程。
2.字节码执行引擎
2.1执行字节码文件
2.2 修改程序计数器对应的值。
2.3 执行垃圾收集线程
3.运行时数据区(内存模型)
3.1堆:存储的对象内存地址
3.2栈(线程栈): 线程运行时就会分配一快对应的栈,用于存储局部变量 (局部变量是则存储的是对象在堆的内存地址 )
3.3本地方法栈:存储的本地方法(java底层的方法)对应的执行地址。
3.4方法区(元空间):常量和静态变量和类信息(堆里面的对象内存地址指针)
3.5程序计数器:用来记录线程正在或马上要运行的代码位置。方法区对应的内存地址。
运行时数据区域
java 栈(线程栈)
1.每个线程执行的时候会分配一个单独的栈空间。
2.每个方法执行同时会创建一个栈帧;
2.1用于存储局部变量表,操作数栈,动态链接,程序计算器等信息。
2.2方法从调用到执行完的过程,对应一个栈帧在java虚拟栈中入栈和出栈的过程。保持先进后出原则。
栈帧中包含:
局部变量表: 记录这变量的数据;
操作数栈:存储需要操作的变量,操作完成后将结果放入局部变量表
动态链接:用于存储方法内有执行其他的方法的内存地址;从JVM中的方法区获取。
ps:动态链接是方法在执行期间将符号转换成方法地址值(因为多台或接口有不同实现,只有执行到方法时才能知道具体实现类)
方法出口:用于执行结束该方法后,保证继续执行调用后的代码地址。
可以通过java -Xss来指定每个线程的java虚拟栈内存
java -Xss2M HackTheJava
本地方法栈
本地方法栈跟java虚拟机栈类似,他们区别是本地方法栈只为本地方法服务;
方法区
存储的常量+静态变量+类信息
程序计数器
用来记录线程正在或马上要运行的代码位置。方法区对应的内存地址。
设计原因:避免其他线程占用资源后等恢复该线程对应得执行位置。
堆
所有引用现存的对象都在这里
GC
1.GC执行:GC是由字节码执行引起执行的垃圾收集线程,该线程为守护线程,执行main方法是开始执行守护线程
整个GC分为MinorGC survivor 区 full gc
GC 触发条件:每次eden 区内存占满后触发
GC可达性分析:
将GC roots 对象作为起点,从这些节点开始向下搜索引用的对象,找到的对像标记为非垃圾对象,其余未标记的对象都是垃圾对象。
GC roots跟节点:线程栈的本地变量,静态变量,本地方法栈的变量等
引用计数器
三色标记法
-
白色:表示还没有被垃圾回收器扫描的对象
-
黑色:表示已经被垃圾回收器扫描过,且对象及其引用的其他对象都是存活的。
-
灰色:表示已经被垃圾回收器扫描过,但对象引用的其他对象尚未被扫描。
在GC开始的时候,先把所有对象都标记为白色,然后从根对象开始去遍历内存中的对象;接着把直接引用的对象标记为灰色然后在判断灰色集合中的对象是否存在子引用。不存在就直接放入黑色集合里面就好了。如果存在就需要子引用的对象放入到灰色集合中。按照这样的步骤不断的进行一个推导。直到灰色集合中的所有对象都会成黑色以后就标记完成。还属于白色的对象就属于不可达对象可以直接被回收。
JVM分代年龄为什么是15次
jvm中heap内存里面分为 eden space 新生代,survivor space青年代,old Gen 老年代
- 新建对象时 jvm会在eden space里面去分配一块内存空间 来存储对象,当eden space内存空间不足时,会触发Young GC进行对象的回收,而那些因为存在引用关系而无法回收的对象。jvm会把它转移到 survivor space里面 。
- survivor space内部分为 from区和To区,那从eden区转移过来的对象会分布到from区。每当触发一次Young Gc那些没有办法被回收的对象就会在from区和To区来回移动没回动一次那么这些对象的Gc年龄就会加一;默认情况下GC年龄达到15的时候;这些对象如果还没办法回收那么JVM呢会把这样一些对象移动到Old Gen里面;其次一个对象的GC年龄是存储在对象头里面的。
- 一个java对象在JVM的内存布局由三部分组成:对象头,实例数据,对齐填充;对象头有4个bit位存储GC年龄,能存储的最大数值是15.既jvm年龄最大时15次。
JVM的运行原理
1.当一个程序启动之前,它的class会被类装载器装入方法区,
2.字节码执行引擎读取方法区的字节码自适应解析,边解析就边运行,
3.pc寄存器指向了main函数所在位置,虚拟机开始为main函数在java栈中预留一个栈帧
4.开始跑main函数,main函数里的代码被执行引擎映射成本地操作系统里相应的实现
5.调用本地方法接口,本地方法运行的时候,操纵系统会为本地方法分配本地方法栈,用来储存一些临时变量,然后运行本地方法,调用操作系统APIi等等。
类的加载过程
参考地址:
类加载总流程
加载
在硬盘中查找并通过IO读入字节码文件,使用到类时才会加载,列如调用类的main()方法,new对象等,在加载阶段会在内存中生成一个代表这个类的java.lang.Class对象,作为方法区这个类的各种数据的访问入口。
通过一个类的全限定名来获取定义此类的二进制流 | 通过包名+类名,获取这个类,准备进行传输 | |
将这个字节流所代表的静态存储结构转换为运行时数据结构 | 在这个类加载到内存中 | |
在内存中生成一个代表这个类的Class对象,任何类被使用时,系统都会为之建立一个Class对象 | 加载完毕创建一个class对象 | |
// 加载过程待补充
类加载器
双亲委派机制
连接
-
验证:
这一阶段为了确保Class文件字节流中信息是否复合虚拟机规范。
2.准备:
初始化静态变量 赋值默认值(类中static的变量)3.解析:
将符号对象引用替换成直接引用;(在解析前变量的类型是通过符号进行占位,在解析的时候将符号变成原本的引用类型)
解析分为动态链接解析和静态链接解析(类加载期间完成),在链接阶段只会进行静态链接解析。
初始化
件字节流中信息是否复合虚拟机规范。
2.准备:
初始化静态变量 赋值默认值(类中static的变量)
3.解析:
将符号对象引用替换成直接引用;(在解析前变量的类型是通过符号进行占位,在解析的时候将符号变成原本的引用类型)
解析分为动态链接解析和静态链接解析(类加载期间完成),在链接阶段只会进行静态链接解析。
初始化
对类的静态变量初始化为指定的值,执行静态代码块。