JVM是什么
java虚拟机,为什么需要虚拟机呢?
由于在不同的操作系统,需要不同的编译。为了适配不同的操作系统,用虚拟机来统一适配编程。
缺点:效率低。不同直接使用操作系统,而需要再次编译才可以。
分类:HotspotVm J9VM ZingVM
JVM的执行流程:将.java文件—编译成.class文件—运行在JVM中 --操作系统中
java,kotlin,Groovy等其他语言都可以编程成.class文件
JVM的内部结构
class文件经过 ----类加载器 进入JVM中
线程共享:堆,方法区
线程独享:java栈,本地方法栈,程序计数器
堆:存放对象的地方(最大内存)
方法区:类信息,常量池,静态变量,编译好的class
java栈:局部变量,方法出口,正在执行的方法内存模型
本地方法栈:本地方法 native–本地库
程序计数器:记录线程运行到的代码行
java栈
(字节码) 栈帧:局部变量,操作数栈,动态库接,返回地址,帧数据区
每一行代码都是一个栈,先赋值运算,入栈操作,得到结果,返回即出栈操作。
一个线程对应一个JVM栈
每调用一次java方法会得到方法局部变量和操作数栈的大小,并根据此分配栈内存。
栈帧在栈顶才是有效的。(当前栈)
类加载与对象创建
类加载
类的生命周期:加载—链接—初始化—使用 --销毁
加载:将java文件编译成.class文件
链接:分为验证,准备,解析
验证:判断字节码文件是否合格(格式是否正确)
准备:静态字段分配字段,并用默认值初始化。
解析:虚拟机常量池内符号引用替换成直接引用(多态机制,编译时转化成运行时)
初始化:类类变量初始化,构造方法和给属性变量赋值。
对象创建(类的实例化)
判断对象对应类是否加载,链接和初始化等一系列操作
为对象分配内存(是否规整。指针碰撞和空闲列表)
处理类安全问题(多线程中找到了相同的内存地址)(CAS算法和本地线程分配缓冲区)
初始化并分配内存空间(初始化零值)
设置对象的对象头(hasCode,所属类,GC分代等信息存在对象头中)
执行init方法进行初始化(构造方法,初始化成员变量等)
对象在JVM的生命周期
创建阶段
应用阶段
不可见阶段 (没有强引用)
不可达阶段 (没强引用,被JVM标记)
收集阶段 (正在收集,通过finalize方法可以监听)
终结阶段
回收算法
对象回收时机
判断一个对象是否可以回收,看当前使用的是否可达当前对象。(对象被引用到)
GC roots: 静态变量 线程栈变量 常量池 JNI 这些变量是否被引用。
一般时看线程栈变量中。
当前对象是否引用到GC roots中的变量。引用到,即可达。不能回收。
标记清除法
GC回收时,扫描对象,判断对象是否被GCroots引用,没被引用即标记。等下次GC到来时,将标记的清除掉。
缺点:内存碎片化问题。
标记整理法
为了解决内存碎片化问题,将标记的变量整理,合理化利用空间。
缺点:效率低。(问题:整理的时候,需要将对象挂起,再改变引用地址)(问题:整理时空间不够,需要二次复制)
复制算法
将内存空间一分为二,一边是使用空间(对象面),一边是空闲面。当GC来的是先标记,清除时,将存活的对象整理到空闲面,然后将对象面不可达对象清空。
缺点:内存消耗大(空间利用率低)效率高
问题:当如果要清除的对象很少时,需要复制的对象太多,而导致效率低。
三种算法比较
效率:复制>标记整理>标记清除
内存整齐度:复制=标记整理>标记清除
内存利用率:标记整理=标记清除>复制
分代回收机制
青年代:频繁创建,死亡率高(看情况,标记整理和标记清除)
老年代:对象稳定,死亡率低(复制算法)