JVM(Java Virtual Machine) Java虚拟机
-
JVM 全称 Java Virtual Machine,也就是我们耳熟能详的 Java 虚拟机。它能识别 .class后缀的文件,并且能够解析它的指令,最终调用操作系统上的函数,完成我们想要的操作。它是可以跨语言跨平台的
过程如下:Java 文件->编译器>字节码->JVM->机器码。
解释执行与JIT的区别:对字节码逐条解释执行,这种方式的执行速度相对会比较慢,尤其当某个方法或代码块运行的特别频繁时,这种方式的执行效率就显得很低。于是后来在虚拟机中引入了JIT编译器(即时编译器),当虚拟机发现某个方法或代码块运行特别频繁时,就会把这些代码认定为“Hot Spot Code”(热点代码),为了提高热点代码的执行效率,在运行时,虚拟机将会把这些代码编译成与本地平台相关的机器码,并进行各层次的优化,完成这项任务的正是JIT编译器。
-
JVM区域划分
- 线程私有的区域:
1.虚拟机栈:虚拟机栈在JVM运行过程中存储当前线程运行方法所需的数据,指令、返回地址。虚拟机栈由一个个方法的存储区域 每个方法为一个栈帧。栈帧包括:操作数栈、局部变量表(存储方法中定义的变量)、动态连接(Java语言特性多态需要类运行时才能确定具体的方法)、完成出口(返回地址)。
2.本地方法栈:调用native方法,本地方法栈跟 Java 虚拟机栈的功能类似,Java 虚拟机栈用于管理 Java 函数的调用,而本地方法栈则用于管理本地方法的调用。但本地方法并不是用 Java 实现的,而是由 C 语言实现的。
本地方法栈是和虚拟机栈非常相似的一个区域,它服务的对象是 native 方法。你甚至可以认为虚拟机栈和本地方法栈是同一个区域。
虚拟机规范无强制规定,各版本虚拟机自由实现 ,HotSpot直接把本地方法栈和虚拟机栈合二为一 。
3.程序计数器:较小的内存空间,当前线程执行的字节码的行号指示器;各线程之间独立存储,互不影响。空间较小 jvm中唯一不会OOM的区域。记录当没有cpu执行权时当前程序执行到哪一条字节码指令。下次拿到执行权继续从这条指令下去
字节码助记码解释地址:https://cloud.tencent.com/developer/article/1333540
在JVM中,基于解释执行的这种方式是基于栈的引擎,这个说的栈,就是操作数栈。
基于栈:移植性好 但是需要编译
基于寄存器:直接执行机器码 速度快 但是移植性差 - 线程共享的区域:
分成方法区和堆区的主要目的是实现动静分离。(堆区一般存放对象实例,回收比较频繁,而方法区 通常存放一些常量)
1.方法区:一般存放 类信息、静态变量、常量、即时编译的代码。
<=jdk1.7一般称为永久代 :受限于堆的大小
>=jdk1.8 一般称为元空间:可使用机器内存 好处方便拓展,坏处挤压堆空间
2.堆:一般存放对象 数组 - HSDB内存查看工具sa-jdi.jar
- 线程私有的区域:
造成内存溢出的几个原因:
1.栈溢出:只压栈不弹栈(导致分配内存空间不足)
2.方法区溢出,CGLIB直接操作字节码运行时,生成大量的动态类。
3.堆溢出:没有连续内存分配给对象,空间不足虚拟机优化技术:
1.方法内联
2.栈帧之间数据共享
java对象的简析(对象的诞生)
-
对象的创建过程:
类加载->检查加载->分配内存->内存初始化->设置(属于哪个class,对象头...)->对象初始化
新建对象分配内存的方式:
1.指针碰撞:移动一个对象大小的空间
2.空闲列表
对象的内存布局:
对象补充:一般对象的大小不是字节的整数倍 会补充到整个对象的大小成为整数倍 -
对象的定位:
1.使用句柄
2.直接指针:java采用采用直接指针
-
判断对象是否存活的方法:
1.引用计数法:当对象被引用时 计数器+1 对象被置空时 计数器-1,计数简单 但是存在 内部互相引用不释放无法判断解开引用的情况
2.可达性分析(根可达),以GCRoots为根
GCRoots:静态变量 线程栈变量 常量池 JNI指针,异常 ,锁
- 常见的各种引用:
1.强引用:
Object o = new Object() ,= 就是强引用
2.软引用:gc时内存不足时会被回收
SoftReference<Object> soft = new SoftReference(o);
3.弱引用:gc时会被回收
WeakReference<Object> weak = new WeakReference(o);
4.虚引用:随时会被回收
PhantomReference<Object> phantom = new PhantomReference(o);
-
对象分配策略:
1.几乎所有的对象都是在堆上分配,符合逃逸分析的会在栈上分配
2.堆区的划分:
对象优先在Eden分配
空间分配担保
大对象直接进入老年代
长期存活的对象进入老年代
动态对象年龄判定
新生代:Eden From To (8:1:1)(采用复制算法)=
老年代:存放长期存活对象和大对象
垃圾回收机制(对象的死亡 GC)
垃圾回收算法:
1.复制算法:直接复制一半有用的内存对象到另外一半内存 然后清空原先的内存
特点:实现简单,运行高效 ,没有内存碎片;但是内存利用率只有一半
2.标记清除算法:对需要回收的对象进行标记进行清除
特点:执行效率不稳定,内存碎片化导致提取GC
3.标记整理算法:标记清除的算法上对内存上的对象进行移动规整
特点:
对象移动
引用更新
用户线程暂停
没有内存碎片
-
分代回收:
之所以新生代分为8:1:1的原因是为了提高效率。大部分eden区的对象存活时间比较短。 如果直接使用复制算法而不开辟from和to区 会导致内存利用率减低
-
对象移动顺序:
-
垃圾回收器:
CMS:
-
STW(stop the world)
暂停所有工作线程
G1:(android 未采用g1)
常量池与String
QA
1. JVM内存结构说一下!
A:从线程私有的 虚拟机栈
虚拟机里会包含栈帧
栈帧又可以分为操作数栈 局部变量表 动态链接 完成出口;
程序计数器 记录当前执行的指令
本地方法栈 主要调用本地的native方法
线程共享的有方法区 存放常量 静态变量 对象引用 和堆 存放对象数组 对象实例
2.什么情况下内存栈溢出?
A:方法只进不出 只压栈不弹栈
3.描述new一个对象的流程!
A: 首先会检查class是否已加载如果未加载 先从classLoader加载class。然后开始分配内存 初始化内存,进行对象头 class实例引用 年龄分代等信息的设置 设置完成后开始对象实例的初始化。
4.Java对象会不会分配在栈中?
A:符合逃逸分析的会被分配在栈中
5. 如果判断一个对象是否被回收,有哪些算法,实际虚拟机使用得最多的是什么?
A:如果使用引用计数法 可以看引用数是否为0 如果使用根可达性分析 则需要根据引用链分析是否可达引用,实际使用的是可达性分析
6.GC收集算法有哪些?他们的特点是什么?
A:复制算法:实现简单 效率块,但是内存利用率低
标记-清除算法:效率不稳定 有内存碎片化 容易导致提前gc
标记-整理算法:重新指向引用,无内存碎片化,用户线程需要暂停,对象会被移动
7.JVM中一次完整的GC流程是怎样的?对象如何晋级到老年代?
A:首先一般的对象分配在新生代的eden区 经过一次垃圾回收后进入 from区 在from区和to区存放对象年龄超过15就会进入老年代 或者 from 区或to区空间不足那么 对象占用较大的会直接晋升老年代
8.Java中的几种引用关系,他们的区别是什么?
A:强弱软虚 强引用要主动释放才会回收 虚引用随时可能被回收 弱引用 gc时会被回收 而软引用会在gc时空间不足被回收
9.final、finally、finalize的区别?
A:final 修饰类 则类不可再被继承,修饰 变量则 变量不可再更改
finally:try{}finally{},try{}catch(XXX){}finally{} 最后都会执行finally块里的内容
finalize:对象被回收的回调 (优先级很低)
10.String s = new String(“xxx”);创建了几个对象?
A:2个 分配xxx到常量池1次 new 指向1次