目录
一、介绍
1.定义
2.组成划分
二、类加载系统
1.类的加载过程
2.类加载器
三、双亲委派机制
过程
双亲委派模型的优点
四、运行时数据区
五、对象的创建流程
六、垃圾回收机制
1.定义
1.1 引用计数法
1.2 可达性分析算法:GC Roots根
2.垃圾回收算法
2.1 标记-清除算法
2.2 复制算法
2.3 标记-整理算法
2.4 分代收集算法
3.垃圾回收器
3.1 Serial收集器
3.2 Parallel收集器
3.3 ParNew收集器
3.4 CMS收集器
3.5 三色标记算法
3.6 垃圾收集器组合方案
一、介绍
1.定义
JVM是什么?
Java Virtual Machine(JVM)即Java虚拟机,是Java程序实现跨平台的一个重要的工具(部件)。
2.组成划分
jvm主要由三个部分组成:类加载系统、运行时数据区、执行引擎。
类加载系统:负责完成类的加载
运行时数据区:在运行Java程序的时候会产生的各种数据会保存在运行时数据区
执行引擎:执行具体的指令(代码)
二、类加载系统
1.类的加载过程
类加载的基本流程
类加载就是java代码会被编辑成.class文件,java程序想要运行起来,就需要让jvm读取到这些.class文件,并且把里面的内容,构造成类对象,保存到内存的方法区中。
A.加载:找到.class文件,打开文件,读取文件内容
B.验证:.class文件是一个二进制的格式(每个字节,都是有特殊含义的)
就需要验证你当前读到的这个格式是否符合要求
C.准备:给类对象分配内存空间(最终的目标,是要构造出类对象)
这里只是分配内存空间,还没有初始化,此时这个空间上的内存的数值,就是全0的
D.解析:针对类对象中包含的字符串常量进行处理,进行一些初始化操作。
java代码中用到的字符串常量,在编译之后,也会进入到.class文件中
E.初始化:针对类对象进行初始化
2.类加载器
三、双亲委派机制
JVM中,内置了,三个类加载器
1.BootStrap ClassLoader 爷
2.Extension ClassLoader 父
3.Application ClassLoader 子
程序员也可以手动创建出新的类加载器
过程
1.给定一个全限定类名,形如java.lang.String
2.从Application ClassLoader作为入口,开始执行查找的逻辑
3.Application ClassLoader,不会立即去扫描自己负责的目录(负责的是搜索项目当前目录和第三方库对应目录),而是把查找的任务,交给它的父亲(Extension ClassLoader )
4.Extension ClassLoader 也不会立即扫描自己负责的目录(负责的是JDK中一些扩展的库,对应的目录),而是把查找的任务,交给它的父亲(.BootStrap ClassLoader )
5..BootStrap ClassLoader 也不想立即扫描自己负责的目录(负责的是标准库的目录)
也想把任务交给自己的父亲,但是它没有父亲,只能亲自负责扫描,标准库的目录
6.没有扫描到,就回到Extension ClassLoader
Extension ClassLoader就会负责扫描负责的扩展库的目录,如果找到,就执行后续的类加载操作,此时查找过程结束
如果没找到,就还是把任务来交给孩子来执行
7.没有扫描到,就回到Application ClassLoader
Application ClassLoader就会负责扫描当前项目和第三方库的目录,如果找到,就会继续后续的类加载操作
如果没找到,就会抛出异常ClassNotFoundExeception
之所以高这一套流程,主要目的,即使为了确保,标准库的类,被加载的有优先级最高,其次是扩展库,其次是自己写的类和第三方库
双亲委派模型的优点
a. 避免重复加载类:比如A类和B类都有⼀个⽗类C类,那么当A启动时就会将C类加载起来,那
么在B类进行加载时就不需要在重复加载C类了。
b. 安全性:使用双亲委派模型也可以保证了Java的核心API不被篡改,如果没有使⽤双亲委派模
型,而是每个类加载器加载自己的话就会出现⼀些问题,比如我们编写⼀个称为java.lang.Object
类的话,那么程序运行的时候,系统就会出现多个不同的Object类,⽽有些Object类又是用户自己提供的因此安全性就不能得到保证了。
四、运行时数据区
堆空间(线程共享):存放new出来的对象
元空间(线程共享):存放类元信息、类的模板、常量池、静态部分
线程栈(线程独享):方法的栈帧
本地方法栈(线程独享):本地方法产生的数据
程序计数器(线程独享):配合执行引擎来执行指令
五、对象的创建流程
类加载校验:校验该类是否已被加载。主要是检查常量池中是否存在该类的类元信息。如果没有,则需要加载。
Student student = new Student(); //这就是一个类加载
分配内存:为对象分配内存。
具体的分配策略:
Bump the Pointer(指针碰撞):如果内存空间的分配是绝对规整的,则JVM记录当前剩余内存的指针,在已用内存分配。
Free List(空闲列表):如果内存空间的分配不规整,则JVM会维护一个可用内存空间的列表用于分配 。
对象并发分配存在的问题:
Compare And Swap(CAS):自旋分配,如果并发分配失败则重试分配之后的地址。
Thread Local Allocation Buffer(TLAB):本地线程分配缓冲,JVM被每个线程分配一块空间,每个线程在自己的空间中创建对象
设置初值:根据数据类型,为对象空间赋初始化值。
设置对象头:为对象设置对象头信息,对象头信息包含以下内容:类元信息、对象哈希码、对象年龄、锁状态标志等。
不开启指针压缩String类型8字节,开启指针压缩后String类型4字节
对于一个类的main方法:
执行init方法:为对象中的属性复制和执行构造方法。
六、垃圾回收机制
1.定义
什么是垃圾?
在堆空间和元空间中,GC这条守护线程会对这些空间开展垃圾回收工作,GC通过两种算法来判断这些空间中的对象是否是垃圾:
1.1 引用计数法
对象被引用,则计数器+1,如果计数器是0,那么对象将被判定为是垃圾,于是被回收。但是这种算法没有办法解决循环依赖的对象。
1.2 可达性分析算法:GC Roots根
gc roots根节点:在对象的引用中,会有这么几种对象的变量:来自于线程栈中的局部变量表中的变量、静态变量、本地方法栈中的变量,这些变量都被称为gc roots根节点。
判断依据:gc在扫描堆空间中的某个节点时,向上遍历,看看能不能遍历到gc roots根节点,如果不能,那么意味着这个对象是垃圾。
对象中的finalize方法:
Object类中有一个finalize方法,也就是说任何对象都有finalize方法。这个方法是对象被回收之前的最后一根救命稻草。
1.GC在垃圾对象回收之前,先标记垃圾对象,被标记的对象的finalize方法将被调用。
2.调用finalize方法如果被对象引用,那么第二次标记该对象,被标记的对象将移除即将被回收的集合,继续存活。
3.调用finalize方法如果对象没有被吸引,那么将会被回收
注意:finalize方法只会被调用一次
对象的逃逸分析:
对象的建立可能不全是在堆上,也可能在栈上。
2.垃圾回收算法
2.1 标记-清除算法
2.2 复制算法
2.3 标记-整理算法
2.4 分代收集算法
老年代满后,往里面再存储,会报错OVM
对象进入老年代条件:
年龄到达15岁;大对象直接进入老年代(可以通过设置参数,来判断哪些是大对象);根据对象动态年龄判断,如果S区中的对象总和超过了S区中的50%,那么下一次做复制的时候,把年龄大于等于这次最大年龄的对象都一次性全部放入老年代。
3.垃圾回收器
3.1 Serial收集器
单线程执行垃圾收集,收集过程中会有较长的STW,在GC时工作线程不能工作。虽然STW较长,但简单、直接。
新生代采用复制算法,老年代采用标记-整理算法。
3.2 Parallel收集器
使用多线程进行GC,会充分利用cpu但是依然会有stw这是jdk8默认使用的新生代和老年代的垃圾收集器。充分利用CPU资源,吞吐量高。
新生代采用复制算法,老年代采用标记-整理算法。
3.3 ParNew收集器
工作原理和Parallel收集器,都是使用多线程GC,但是区别在于ParNew收集器可以和CMS收集器配合工作。
主流方案为:ParNew收集器负责收集新生代,CMS负责收集老年代。
3.4 CMS收集器
目标:尽量减少stw的时间,提升用户体验。真正做到gc线程和用户线程几乎同时工作。CMS采用标记-清除算法。
出现stw的是初始标记和重新标记两部分,并发清理是最浪费时间的
重新标记是希望更准确的,不能出现并发,所以会有stw
3.5 三色标记算法
在并发标记阶段,对象的状态可能发生改变,GC在进行可达性分析算法分析对象时,用三色来标识对象的状态
黑色:这个对象及其所有引用都已被GC Roots遍历,黑色的对象不会被回收
灰色:这个对象被GC Roots遍历过但其部分的引用没有被GC Roots遍历,在重新标记时重新遍历灰色对象
白色:这个对象没有被GC Roots遍历过,在重新标记时该对象如果是白色的话,那么将会被回收