jvm虚拟机

作为一个Java后端攻城狮,jvm是必须要懂的知识点。我主要是看书+看视频结合起来学习,书看的是『深入理解java虚拟机第三版』,视频看的是宋红康的jvm教程。
宋红康jvm教程

接下来就开始漫长的jvm学习之旅吧(◕ᴗ◕✿)

内存与垃圾回收篇

1 JVM与Java体系结构

比较重要的两个概念:虚拟机、垃圾回收器
Java虚拟机:它是一台虚拟的计算机,专门为执行单个计算机程序而设计,在Java虚拟机中执行的指令称为Java字节码指令。jdk8用的虚拟机是Hotspot
image.png


jvm的整体结构
image.png
jvm的架构模型
image.png
现在的虚拟机一般都包括解释器和编译器,解释器响应速度快,编译器提升的是性能
比较流行的三个商用虚拟机:Hotspot、JRocket、J9

2 类加载子系统

image.png
验证:验证字节码文件是否正确
准备:初始化变量,赋默认值

虚拟机自带的加载器
image.png

image.png

双亲委派机制

image.png
为了防止项目被恶意攻击,引入双亲委派机制,把请求交给父类处理。倘若父类加载器无法完成任务,子类加载器才会去加载。
image.png
自定义对象的加载器就是系统加载器Application ClassLoader
image.png

沙箱安全机制:对java核心源代码的保护
image.png
字节码文件是放到方法区进行保存的,还有类加载器信息也是放在方法区

3 运行时数据区

image.png
举例:虚拟机就是1个进程,1个进程有5个线程,就有5组虚拟机栈。这些线程共用方法区和堆区。
重点优化主要在于堆和方法区。95%垃圾回收发生在堆,5%发生在方法区。jdk8后方法区被替换为元空间,元空间存在于本地内存,意味着只要本地内存足够,它不会出现像永久代中”java.lang.OutOfMemoryError: PermGen space”这种错误。

4 程序计数器

程序计数器,也称作PC寄存器
栈和程序计数器是没有垃圾回收的,堆和方法区有垃圾回收
javap -v对字节码文件进行解析,要切换到class的目录里
java文件反编译的话使用jd-gui
image.png
指令地址是存在程序计数器中的。程序计数器用来存储指向下一条指令的地址。因为cpu不断切换各个线程,就需要程序计数器记录当前执行的指令地址。每个线程分配一个程序计数器
image.png

image.png

5 虚拟机栈

虚拟机栈概述

虚拟机栈保存方法的局部变量、部分结果,对应着一次次Java方法的调用
image.png
栈是运行时的单位,堆是存储时的单位
一般来说数据区堆的占比是比较大的,现在元空间改用本地内存后,空间大大增加
image.png

栈的优点、可能出现的异常

image.png
image.png
虚拟机调优:
image.png
不同线程之间的栈帧是不允许相互引用的

栈的内部结构

虚拟机栈内部包括局部变量表、操作数栈
image.png
image.png

局部变量表

image.png
javap对字节码文件进行解析,可以看到局部变量、栈帧信息、指令地址
局部变量表最基本的存储单元是Slot(变量槽) 8个字节对应两个槽,从第一个槽读取
this变量不存在于当前方法的局部变量表中

操作数栈

image.png
操作数栈在编译的时候,长度就确定了
++i 和 i++如果不进行赋值,它们的效果是一样的

动态链接

指向运行时常量池的方法引用,运行时常量池存在于方法区

方法的调用

image.png

Java是静态语言,静态类型语言检查在编译期,动态类型语言检查在运行期
image.png
image.png
两个线程公用一个资源,有可能线程不安全。线程不安全的原因是两个线程对同一个对象进行操作,产生冲突

6 本地方法接口

native关键字修饰的方法,一个Native Method就是一个Java调用非Java代码的接口
Native Method就是调用了底层的C、C++方法
Java与底层操作系统交互,可能要用到C的接口

7 本地方法栈

Java虚拟机栈用于管理Java方法的调用,本地方法栈用于管理本地方法的调用

8 堆

1.堆的核心概述

概述

一个进程对应一个jvm实例,对应一个运行时数据区。一个进程里有多个线程,多个线程共享堆和方法区。共享就要考虑线程安全问题
堆在创建时空间大小就被分配了,堆的空间大小可以调节
javac 编译 java运行
堆可以处于物理上不连续,逻辑上连续
对象实例和数组存放在堆上
image.png
栈中存放的是对象的引用,堆中存放的是对象的实例。当对象使用完后,对象指向堆的引用就被断开,这个对象
当堆空间不够用的时候,会触发GC,判断是否成为垃圾需要回收。GC不能频繁触发,会影响用户线程的效率。栈中只存在入栈出栈的操作,没有垃圾回收

内存细分

image.png
jdk7的时候叫永久代,jdk8的时候叫元空间
逻辑上新生代、老年代、元空间属于堆,实际上只管辖新生代、老年代。元空间属于方法区的范畴
在VM options中设置虚拟机运行参数

2.设置堆内存大小与OOM

-Xms用来设置堆空间的起始内存大小,-Xmx用来设置堆空间的最大内存大小
默认堆空间大小:
初始内存:电脑内存大小/64
最大内存:电脑内存大小/4

开发中建议将初始堆内存和最大堆内存设置成相同的值。避免GC扩容造成系统不必要的占用。
s0(from)和s1(to)由于复制算法的原因,二选一使用,有一个是不存放数据的。因此实际计算的最大内存比设置的小。
-XX:+PrintGCDetails 打印GC过程中的细节
Throwable分为Exception和Error
image.png
默认情况下:
新生代:老年代=1:2
Eden:s0:s1=8:1:1
image.png
没有引用指向的堆内存叫做垃圾
s0、s1为空的区域称为to区,年龄计数器达到15,对象进入老年代。Eden满了会触发Young GC,s0区垃圾回收是被动的,Young GC也触发so垃圾回收。老年代很少发生GC,大部分回收的都是新生代
总结
image.png

超大对象有可能直接进入老年代,Eden存活的对象如果放不进so、s1,也有可能直接晋升老年代。老年代发生的垃圾回收是Full GC。发生10次Young GC,才有可能发生一次Full GC
老年代满了就会报OOM错误。调优就是让GC的次数尽量少一些,让用户线程执行的效率更高
Full GC收集整个堆空间和方法区的垃圾回收
如果Full GC后空间还是不足,就会报OOM

分代的理由就是优化GC性能

3.内存分配策略

image.png
举例:
byte[] byte = new byte[1024 * 1024 * 20] 就是一个20M的大对象,如果该对象大于Eden区内存,就会直接被分配到老年代
TLAB是虚拟机在堆内存的eden划分出来的一块专用空间,是线程专属的。TLAB一般占Eden的1%,是被每个线程私有的空间
image.png
image.png

逃逸分析

image.png

这个例子new出的对象,作用域只在当前方法有效,没有发生逃逸,因此对象存在栈中。
image.png
开启逃逸分析后,未发生逃逸的对象会分配到栈上,提高程序效率。但这种情况发生的较少,大部分实例对象还是存储在堆上的。

代码优化

image.png
标量是指一个无法再分解为更小数据的数据
局部变量存储在栈中

9 方法区

方法区的内部结构:永久代->元空间
方法区中存放程序加载的类的信息

1.栈、堆、方法区的交互关系

image.png
image.png
image.png
永久代可能发生OOM,元空间使用本地内存
image.png
方法区中存储类型信息、方法信息(方法修饰符、返回类型等)、运行时常量池
jdk8后字符串常量池、静态变量存放到堆中了
image.png

2.运行时常量池

字节码文件直接打开是乱码的,要反编译后才能看到正常的字节码文件信息
字节码文件的常量池存放编译期生成的各种数值量和对类型、方法的符号引用
image.png
image.png

Hotspot中方法区的变化:
image.png

永久代为什么被元空间替代?
对永久代设置空间大小是很难确定的;对永久代进行调优是很困难的

元空间使用本地内存,大大减少GC的次数
full gc是老年代、元空间空间不足时才会触发
方法区的垃圾回收主要包括两部分内容:常量池中废弃的常量和不再使用的类型

10 对象的实例化内存布局与访问定位

1.对象的实例化

image.png
image.png

2.对象的内存布局

image.png
类的对象头包含什么?
运行时元数据、类型指针。如果是数组,还需记录数组的长度

3.对象访问方式

句柄访问、直接指针

11 直接内存

直接内存是在Java堆外的、直接向系统申请的内存空间
image.png
IO阻塞,NIO非阻塞

image.png

12 执行引擎

image.png
Java字节码的执行是由执行引擎完成的

Java为什么是半解释半编译的语言?
Java字节码文件既可以由解释器执行,也可以由JIT即时编译器执行

汇编语言

机器码是由二进制编码方式表示的指令,能被计算机直接识别
汇编语言、高级语言都需要翻译成机器指令,才能被cpu解释执行

解释器

image.png
Java字节码是由解释器翻译成平台对应的本地机器指令执行,由PC计数器记录下一条需要被解释的字节码指令

JIT编译器

image.png
image.png
解释器响应快,JIT编译器效率高

那么,什么时候使用解释器,什么时候使用即时编译器呢?
采用基于计数器的热点探测,符合条件就把代码翻译成机器指令,并且缓存

设置程序执行方式
image.png

JIT编译器的分类
image.png
64位的jdk只能采用-server的JIT编译器
C1就是-client方式,C2就是-server方式

13 String Table

1.String的基本特性

image.png
jdk9中关于String的变化:char[]两个字节,byte[]一个字节,byte加上编码标记,节约了内存
image.png

当字符串常量池中的字符串很多时,提高StringTableSize,性能会提高
image.png
如果是成员变量,那么不分基本类型和引用类型都是在java的堆内存里面分配空间,而局部变量的基本类型是在栈上分配的。
栈中存放对象引用和局部变量,对象引用指向堆

2.字符串的拼接操作

image.png
image.png

拼接操作出现变量的情况下,左右两边变量是不相等的
String字符串拼接的过程出现变量,会创建一个StringBuilder、String对象
image.png
StringBuilder的append()方法自始至终只使用一个对象,最终打印字符串需要使用toString()方法
开发当中,如果大概确定字符串的长度,建议构造器设置StringBuilder的长度,避免扩容影响效率

3.intern()的理解

image.png
intern()方法的作用是在字符串常量池中创建字符串常量
image.png
image.png
对于程序中大量存在的重复字符串,使用intern()赋值字符串,可以节约内存空间和执行时间

14 垃圾回收概述

类的加载器将字节码文件加载到内存中,再由执行引擎解释执行

1.什么是垃圾

垃圾是指运行程序中没有任何指针指向的对象

2.为什么需要GC

image.png
垃圾回收只作用于方法区和堆,堆是垃圾收集器的工作重点

15 垃圾回收相关算法

1.垃圾标记阶段:对象存活判断

image.png

引用计数算法

Java没有使用这种算法,这样很难处理循环引用关系,可能导致内存泄漏
image.png
内存泄漏:对象已经没有价值了,但不能被GC
image.png

可达性分析算法

Java使用这种算法,解决循环引用的问题,防止内存泄漏的发生
image.png
image.png

2.对象的finalization机制

垃圾回收对象之前,总会先调用这个对象的finalize()方法,交给垃圾回收器去调用即可。
可达性分析算法:如果从所有的根节点都无法访问到某个对象,说明对象已经不再使用了,但不一定这个对象就是非回收不可的,还会调用finalize()方法进行判断,对象存在三种状态。
image.png

两次标记过程判断对象是否需要回收
image.png
第一次标记,调用finalize()方法,对象可复活。第二次标记,由于finalize()已经调用,对象就需要被回收
Jprofile JVM的调优工具,要求熟练使用,至少会用一款工具即可。

堆空间的实体没有对象的引用,就需要被回收

3.垃圾清除阶段:标记-清除算法、复制算法、标记-整理算法

标记-清除算法

image.png
标记-清除算法中:不可达的对象会被垃圾回收,标记的对象是可达对象
效率不太高,需要遍历两次,标记一次,清除一次,并且需要stw
image.png

复制算法

复制算法的高效性是建立在存活对象少、垃圾对象多的前提下
image.png
新生代的survivor区使用复制算法非常高效

标记-整理算法

不仅清除了垃圾,还进行内存碎片的整理,效率会比标记-清除算法低一些
image.png

4.分代收集算法

Java虚拟机的垃圾回收使用的是分代收集算法
image.png

标记清除算法,标记和清除时都要处于stop the world状态,用户线需要停止。

5.增量收集算法

image.png
image.png

image.png

16 垃圾回收相关概念

1.System.gc()的理解

Full GC就是对老年代进行垃圾回收,一般也会对新生代进行垃圾回收。
调用这个方法,会触发Full GC,会对老年代和新生代进行垃圾回收。附带免责声明,提醒jvm进行垃圾回收,但是不确定是否马上执行gc

2.内存溢出与内存泄漏

内存溢出(OOM):没有空闲内存,并且垃圾收集器也无法提供更多内存
image.png
内存泄漏:只有对象不会再被程序用到,但是GC又不能回收他们的情况,才叫内存泄漏。内存泄漏的数据比较多的时候,是有可能导致OOM的。
举例:
image.png

3.Stop The World

image.png

4.垃圾回收的并行与并发

并发

同一个程序段内几个程序运行
image.png

并行

同一时间几个程序运行
决定并行的因素是CPU的核心数量,多个核就可以同时进行多个进程

5.安全点与安全区域

image.png
image.png

6.Java中引用的概念

image.png
以上四种引用,都是在引用关系还存在的情况下进行讨论,即可达的情况
栈帧中的局部变量表指向堆空间的对象

强引用

GC Roots中的引用指向堆空间的对象,属于强引用的范畴
日常开发中99%的对象都是强引用
强引用是造成内存泄漏的主要原因

软引用

系统将要发生内存溢出之前,才会把软引用对象回收。当内存够的时候,不会清除软引用
image.png

弱引用

弱引用关联的对象只能生存到下一次垃圾收集发生为止
软引用、弱引用非常适合来保存那些可有可无的缓存数据

虚引用

虚引用即对象回收跟踪
image.png
守护线程:当程序中没有非守护线程时,守护线程也执行结束

17 垃圾回收器

Java发展至今已经衍生了众多的GC版本
jdk8默认的垃圾回收器是并行回收器,新生代用Parallel GC、老年代用Parallel Old GC。这两个垃圾回收器是搭配使用,互相激活
jdk9默认的垃圾回收器是并发回收器,使用G1 GC

1.Java8的新特性

image.png

2.垃圾回收器的分类和性能指标

分类

image.png

性能指标

image.png
image.png

3.垃圾回收器的介绍

image.png
并发:用户线程和垃圾回收线程可以同时执行
image.png

CMS回收器

CMS GC作用于老年代,使用标记-清除算法
image.png
image.png

G1回收器:区域化分代式

Parallel 和 Parallel Old垃圾回收可以达到吞吐量最优
G1的优点:
image.png
G1的分区:堆空间被分为若干个区域
image.png
image.png
image.png
G1的缺点:
image.png
G1回收器的参数设置:
image.png
image.png
G1回收器作用于新生代和老年代
G1回收器垃圾回收过程:新生代GC + 并发标记过程 + 混合回收
image.png
G1的初衷就是要尽量避免Full GC,降低暂停时间,提高用户使用流畅度
ZGC比G1流畅度提升不少,ZGC还处于Oracle的测试已当中

4.垃圾回收器总结

image.png
image.png
image.png
jdk8可以通过设置虚拟机参数使用G1回收器

5.GC日志分析

image.png
GCViewer、GCEasy
image.png

6.垃圾回收器发展

image.png
image.png

字节码与类的加载篇

1 Class文件结构

Java虚拟机:跨平台的语言。可以处理很多不同的语言
Java虚拟机官方规范
JVM引入JIT即时编译器,效率高了很多
栈的局部变量表和操作数栈
Integer的范围是-128-127,超出范围返回一个new的Integer对象
字节码文件是实现java语言跨平台性的本质

Class文件的标识:魔数

Class文件版本号:主版本一般是会改变的(魔数+主版本+副版本)
高版本的虚拟机可以解析低版本的字节码文件

Demo字节码的解析:比较复杂,了解即可—-常量池数据的解读
使用jclasslib工具解析会更加方便
image.png

2 访问标识