目录
一、概念
Java虚拟机,模拟CPU执行指令的程序
tips:JDK、JRE、JVM的区别
JDK包含JRE,JRE包含JVM。JDK下的文件有jre文件,有bin文件;在bin文件中有java.exe(在jre文件下也有bin,bin下也有java.exe),通过java.exe程序来运行xxx.class字节码,启动的时候会创建一个JVM。
二、JVM执行流程
class文件加载到Java进程的内存中(运行时数据区)
线程共享的:堆、方法区(gc只回收这两个区的)
线程私有的:虚拟机栈、本地方法栈、程序计数器
1.堆
使用new创建的对象(数组等),jdk1.8中常量池也是在堆中,随着程序运行创建,退出而销毁,堆中数据只要还在使用就不会销毁。
tips:对象是等号右边的;变量是等号左边的
在gc(垃圾回收)中,堆会分为两个区域:新生代、老年代。新生代中放新建的对象,经过一定gc次数后会放入老年代
2.方法区
存放类(把类加载到方法区,保存类的代码数据;在堆里边生成一个Class<类名>的类对象)、常量、静态变量
在gc时,方法区也叫永久代(jdk1.7)(还属于Java进程内存),元空间(jdk1.8)(不属于Java内存是在系统/本地内存)
3.Java虚拟栈
方法运行结束,栈帧被销毁,栈帧保存的数据也被销毁
(1)生命周期与线程相同
(2)线程(main)执行某个方法,就创建该方法的栈帧(入栈),方法返回就出栈
(3)虚拟机栈包含:
a.局部变量表(表中含有变量(=左边的)、基础数据类型的值(如果是基础数据类型,=右边的也在虚拟机栈))
b.操作栈:每个方法生成的一个先进后出的操作栈
c.动态链接:指向运行时常量池的方法引用
利用intern把数据放到运行时常量池
StringBuilder s = new StringBuilder();
s.append("a");
s.toString().intern();
d.方法返回地址:pc寄存器的地址(指向下一个指令的地址)
4.本地方法栈
调用除Java外的语言的函数就是使用本地方法栈
5.程序计数器
用于标识程序行号
6.异常情况
(1)内存溢出VS内存泄漏
OOM(内存溢出):存放的数据大小超出该区域的内存大小(除了程序计数器,其他都有可能发生OOM),结果恶劣,要避免
内存泄漏:线程生命周期太长,始终引用一些不使用的数据(gc无法回收),可用空间减少,最后可能导致OOM
解决内存泄漏:重启程序、修改bug
(2)OOM VS StackOverFlow
StackOverFlow:某个线程调用方法,会创建该方法栈帧入栈,如果栈中的栈帧数量超过jvm规定,会报该异常
eg:递归返回逻辑不正确
public void static main(String args[]){
print();
}
public void print(){
print();
}
三、JVM类加载
1.加载
(不是类加载)
加载class字节码到Java进程的内存中,在堆中创建类对象
2.验证
验证字节码是否符合JVM的规范
3.准备
为静态变量分配内存和初始值
eg:(1)static常量:赋值为初始值
public static final Integer x=123;
(2)static变量:赋值为缺省值
public static Integer x=123;#变量赋值为Integer的缺省值null
public static int x=123;#变量赋值为int的缺省值0
4.解析
5.初始化
(不是对象的初始化,是类对象的初始化:执行静态变量(真正赋值)、静态代码块等)
6.类加载机制
(1)双亲委派模型(jdk类加载的默认机制)
优点:安全
确保优先采取以上前三种类加载器加载类,如果是自定义类加载器,若遵循双亲委派模型,则不会加载到自定义的java.lang.Object,而是jre中的(安全);若不遵循,则加载到自定义的Object(不安全);但是jdk中,自定义类加载器进行了安全校验:加载类,如果类的全限定名(包名+类名)以java./javax. 开头的包就报错。
缺点:扩展性/灵活性不好
某些场景下,无法事先知道需要加载的类名
(2)破坏双亲委派模型:针对双亲的缺点
SPI机制
四、垃圾回收(区域空间不足,触发该区域的gc)
堆、方法区需要垃圾回收。但是对于方法区很少回收,主要回收堆。
1.不同变量创建和销毁
public class A{
private Preson x= new Person();#x是成员变量,创建类A的对象时才创建,A的对象销毁时销毁
public static Person y= new Person();#y是静态变量,类加载时创建,类销毁时销毁
public static void xxx(){
Person z= new Person(); #z局部变量,执行到这个代码时创建,出了作用范围销毁
2.判断是否垃圾
(1)引用计数算法
一个对象被引用一次计数器+1,如果为0则是垃圾可回收
缺点:无法解决循环引用问题(未被JVM引用)
(2)可达性分析
3.老年代和新生代
老年代:(1)大对象:占据的空间超过jvm规定阈值
(2)新生代对象存活超过15次
(3)新生代gc时分配担保失败的对象:新生代90%空间存活对象,放不下的进入老年代
4.垃圾回收算法
(1)标记清除算法(老年代回收算法)
a.标记要回收的对象 b.统一回收
缺点:效率不高、产生大量内存算法
(2)复制算法(新生代回收算法)
将内存某个区域划分成两个大小相同的空间,每次只使用其中一个,回收的时候把存活的对象复制到另一个不用的空间,清除之前的整个空间
适用于大多数对象,朝生夕死,比如局部变量,方法执行完,对象是垃圾
缺点:空间利用率不高
JVM中,Eden与Survivor的大小比例是8 : 1,每次新生代可用内存空间为整个新生代容量的90%,而剩下的10%用来存放回收后存活的对象。 每次使用E区和一个S区
(3)标记整理算法(老年代回收)
a.标记 b.整理:把存活对象往一端移动,清理掉端外的空间
(4)分代算法
新生代采用复制算法,老年代采取标记清除/标记整理
5.垃圾回收线程(守护线程)
一个进程中存在非守护线程,进程就不会结束
垃圾回收线程可以并行执行,但是用户线程有可能并行执行、暂停(SWT)
吞吐量:cpu执行用户代码时间/cpu执行整个进程的时间
吞吐量越高,程序性能越好
6.垃圾回收器
(1)Serial收集器:新生代收集器(复制算法)
单线程(单个垃圾回收线程),大多数电脑支持多线程,所以这个收集器效率不高,不常使用
(2)ParNew:新生代收集器(复制算法)
多线程搭配CMS(老年代收集器)
(3)Parallel Scanvenge:新生代收集器(复制算法)
吞吐量优先(适用于性能优先的程序),搭配Parallel Old
(4)Serial Old:老年代收集器(标记整理算法)
单线程
(5)Parallel Old:老年代收集器(标记整理算法)
吞吐量优先
(6)CMS收集器(重点)
老年代收集器,并发GC,标记清除算法,用户体验优先(整体是并发,即垃圾回收线程和用户线程同时进行,有局部的stw(少许时间暂停用户线程))
搭配新生代的ParNew收集器
步骤:a.初始标记:标记GC Roots能直接关联的对象,需要STW
b.并发标记:进行GC Roots引用链追踪的过程(搜索引用路径)
c.重新标记:修复第二个阶段用户线程同时执行时,产生变动的记录,需要STW
d.并发清除垃圾
缺陷:a.CPU敏感,吞吐率低,CPU效率低
b.无法处理浮动垃圾(第四个阶段用户线程并发执行产生的垃圾在此次GC无法回收)
c.产生大量空间碎片
(7)G1收集器:唯一的全区域垃圾回收器(重点)
步骤:
新生代回收:回收多个E区+多个S区,复制存活对象到空的region区(动态指定为S区)
老年代回收:a.初始标记:类似CMS的初始标记,但是可以和新生代gc同时执行
b.并发标记:类似CMS的并发标记,多了优先回收,可以直接回收存活率低/无存活率的region
c.最终标记:类似CMS
d.筛选回收:筛选存活率低的region回收
理解垃圾回收:在垃圾回收线程中,使用垃圾回收器(垃圾车师傅),先找到垃圾(可达性分析算法),再回收垃圾(采取什么垃圾回收算法)