共计 17107 个字符,预计需要花费 43 分钟才能阅读完成。
送大家以下学习材料,文末有支付形式
初识 JVM 标准
从三种角度意识 JVM
在这里插入图片形容
JVM 概述
- JVM:Java Virtual Machine,也就是 Java 虚拟机
- 所谓虚拟机是指:通过软件模仿的具备残缺硬件零碎性能的、运行在一个齐全隔离环境中的计算机系统
- JVM 是通过软件来模仿 Java 字节码的指令集,是 Java 程序的运行环境
JVM 次要性能
- 通过 ClassLoader 寻找和装载 class 文件
- 解释字节码成为指令并执行,提供 class 文件的运行环境
- 进行运行期间的内存调配和垃圾回收
- 提供与硬件交互的平台
虚拟机是 Java 平台无关的保障
在这里插入图片形容
JVM 标准作用及其外围
JVM 标准作用
- Java 虚拟机标准为不同的硬件提供了一种编译 Java 技术代码的标准
- 该标准使 Java 软件独立于平台,因为编译时针对作为虚拟机的“个别机器”而做
- 这个“个别机器”可用软件模仿并运行于各种现存的计算机系统,也可用硬件来实现
JVM 标准定义的次要内容
- 字节码指令集
- Class 文件的格局
- 数据类型和值
- 运行时数据区
- 栈帧
- 非凡办法
- 类库
- 异样
- 虚拟机的启动、加载、链接和初始化
- …
Class 字节码解析
Class 文件格式概述
- Class 文件是 JVM 的输出,Java 虚拟机标准中定义了 Class 文件的构造,Class 文件是 JVM 实现平台无关、技术无关的根底
- 无符号数:根本数据类型,以 u1、u2、u4、u8 来代表几个字节的无符号数
- 表:由多个无符号和其余表形成的合乎数据类型,通常以 “\_info” 结尾
- Class 文件是一组以 8 字节为单位的字节流,各个数据我的项目按序紧凑排列
- 对于占用空间大于 8 字节的数据项,依照高位在前的形式宰割成多个 8 字节进行存储
- Class 文件格式外面只有两种类型:无符号数、表
Class 文件的格局
- javap 工具生成非正式的”虚拟机汇编语言“,格局如下:
- [[]…]] [comment]
- 是指令操作码在数组中的下标,该数组以字节模式来存储以后办法的 Java 虚拟机代码;也能够是相当于办法起始处的字节偏移量
- 是指令的助记码、是操作数、是行尾的正文
Class 文件格式阐明
- constant\_pool\_count:是从 1 开始的
- 不同的常量类型,用 tag 来辨别,它前面对应的 info 构造是不一样的
- L 示意对象,[示意数组、V 示意 void
- stack:办法执行时,操作栈的深度
- Locals:局部变量所需的贮存空间,单位是 slot
- slot 是虚拟机为局部变量分配内存所应用的最小单位
- args\_size:参数个数,为 1 的话,因实例办法默认会传入 this,locals 也会预留一个 slot 来寄存
ASM
“
参考博文
ASM 概述
- ASM 是一个 Java 字节码操纵框架,它能被用来动静生成类或者加强既有类的性能
- ASM 能够间接产生二进制 class 文件,也能够在类被加载入虚拟机之前动静扭转类行为,ASM 从类文件中读入信息后,可能扭转类行为,剖析类信息,甚至能依据要求生成新类
- 目前许多框架如 cglib、Hibernate、spring 都间接或间接地应用 ASM 操作字节码
ASM 编程模型
- Core API:提供了基于事件模式的编程模型。该模型不须要一次性将整个类的构造读取到内存中,因而这种形式更快,须要的内存更少,但这种编程形式难度较大
- Tree API:提供了基于树型的编程模型。该模型须要一次性将一个类的残缺构造全副读取到内存中,所以这种办法须要更多的内存,这种编程形式较简略
ASM 的 Core API
- ASM Core ApI 中操纵字节码的性能基于 ClassVisitor 接口。这个接口中的每个办法对应了 class 文件中的每一项
- ASM 提供了三个基于 ClassVisitor 接口的类来实现 class 文件的生成和转换
- ClassReader:ClassReader 解析一个类的 class 字节码
- ClassAdapter:ClassAdapter 是 ClassVisitor 的实现类,实现要变动的性能
- ClassWriter:ClassWriter 也是 ClassVisitro 的实现类,能够用来输入变动后的字节码
- ASM 给咱们提供了 ASMifier 工具来帮忙开发,可应用 ASMifier 工具生成 ASM 构造来比照
类加载、连贯和初始化
类加载和类加载器
- 类被加载到 JVM 开始,到卸载出内存,整个生命周期如图:
在这里插入图片形容
- 加载:查找并加载类文件的二进制数据
- 连贯:就是将曾经读入内存的类的二进制数据合并到 JVM 运行时环境中去,蕴含以下步骤:
- 验证:确保被加载类的正确性
- 筹备:为类的 动态变量 分配内存,并初始化
- 解析:把常量池中的符号援用转换成间接援用
- 初始化:为类的动态变量赋初始值
类加载要实现的性能
- 通过类的全限定名来获取该类的二进制字节流
- 把二进制字节流转化为办法区的运行时数据结构
- 在堆上创立一个 java.lang.Class 对象,用来封装类在办法区内的数据结构,并向外提供了拜访办法区内数据结构的接口
加载类的形式
- 最常见的形式:本地文件系统中加载、从 jar 等归档文件中加载
- 动静的形式:将 java 源文件动静编译成 class
- 其余形式:网络下载、从专有数据库中加载等等
类加载器
- Java 虚拟机自带的加载器包含以下几种:
- 启动类加载器(BootstrapClassLoader)
- 平台类加载器(PlatformClassLoader)JDK8:扩大类加载器(ExtensionClassLoader)
- 应用程序类加载器(AppClassLoader)
- 用户自定义的加载器:是 java.lang.ClassLoader 的子类,用户能够定制类的加载形式;只不过自定义类加载器其加载的程序是在所有零碎类加载器的最初
类加载器的关系
在这里插入图片形容
类加载器应用
类加载器阐明
- 启动类加载器:用于加载启动的根底模块类,比方:java.base、java.management、java.xml 等
- 平台类加载器:用于加载一些平台相干的模块,比方:java.scripting、java.compiler *、java.corba * 等
- 应用程序类加载器:用于加载利用级别的模块,比方:jak.compiler、jdk.jartool、jdk.jshell 等等;还加载 classpath 门路中的所有类库
- JDK8:启动类加载器:负责将 <JAVA\_HOME>/lib,或者 -Xbootclasspath 参数指定的门路中的,且是虚拟机辨认的类库加载到内存中(依照名字辨认,比方 rt.jar,对于不能辨认的文件不予装载)
- JDK8:扩大类加载器:负责加载 <JRE\_HOME>/lib/ext,或者 java.ext.dirs 零碎变量所指定门路中的所有类库
- JDK8:应用程序类加载器:负责加载 classpath 门路中的所有类库
- Java 程序不能间接援用启动类加载器,间接设置 classLoader 为 null,默认就应用启动类加载器
- 类加载器并不需要等到某个类“首次被动应用”的时候才加载它,JVM 标准容许类加载器在预料到某个类将要被应用的时候就事后加载它
- 如果在加载的时候 .class 文件缺失,会在该类首次被动应用时报告 LinkageError 谬误,如果始终没有被应用,就不会报错
双亲委派模型
- JVM 中的 ClassLoader 通常采纳双亲委派模型,要求除了启动类加载器外,其余的类加载器都应该有本人的父级加载器。这里的父子关系是组合而不是继承,工作过程如下:
- 一个类加载器接管到类加载申请后,首先搜寻它的内建加载器定义的所有“具名模块”
- 如果找到了适合的模块定义,将会应用该加载器来加载
- 如果 class 没有在这些加载器定义的具名模块中找到,那么将委托给父级加载器,直到启动类加载器
- 如果父级加载器反馈它不能实现加载申请,比方在它的搜寻门路下找不到这个类,那子类加载器才本人来加载
- 在类门路下找到的类将成为这些加载器的无名模块
- 双亲委派模型对于保障 Java 程序的稳固运作很重要,能够防止一个类被加载屡次
- 实现双亲委派的代码在 java.lang.ClassLoader 的 loadClass() 办法中,如果自定义类加载器的话,举荐笼罩实现 findClass() 办法
- 如果有一个类加载器能加载某个类,称为 定义类加载器,所有能胜利返回该类的 Class 的类加载器 都被称为初始类加载器
双亲委派模型的阐明
- 双亲委派模型对于保障 Java 程序的稳固运作很重要
- 实现双亲委派的代码 java.lang.ClassLoader 的 loadClass() 办法中,如果自定义类加载器的话,举荐笼罩实现 findClass() 办法
- 如果有一个类加载器能加载某个类,称为 定义类加载器,所有能胜利返回该类的 Class 的类加载器 都被称为 初始化加载器
- 如果没有指定父加载器,默认就是启动类加载器
- 每个类加载器都有本人的命名空间,命名空间由该类加载器及其所有父加载器所加载的类形成,不同的命名空间,能够呈现类的全路径名 雷同的状况
- 运行时包由同一个类加载器的类形成,决定两个类是否属于同一个运行时包,不仅要看全路径名是否一样,还要看定义类加载器是否雷同。只有属于同一个运行时包的类能力实现互相包内可见
毁坏双亲委派模型
- 双亲委派模型有一个问题:父加载器无奈向下辨认子加载器加载的资源
- 为了解决这个问题,引入了线程上下文类加载器,能够通过 Thread 的 setContextClassLoader() 进行设置
- 实现热部署时,比方 OSGI 的模块化热部署,它的类加载器就不再是严格依照双亲委派模型,很多可能就在平级的类加载器中执行了
类连贯和初始化
类连贯次要验证的内容
- 类文件构造查看:依照 JVM 标准规定的类文件构造进行
- 元数据验证:对字节码形容的信息进行语义剖析,保障其合乎 Java 语言标准要求
- 字节码验证:通过对数据流和控制流进行剖析,确保程序语义是非法和合乎逻辑的。这里次要对办法体进行校验
- 符号援用验证:对类本身以外的信息,也就是常量池中的各种符号援用,进行匹配校验
类连贯中的筹备
- 为类的 动态变量 分配内存,并初始化
类连贯中的解析
- 解析就是把常量中的符号援用转换成间接援用的过程,包含:符号援用:以一组无歧义(惟一)的符号来形容所援用的指标,与虚拟机的生效无关
- 间接援用:间接执行指标的指针、绝对偏移量、或是能间接定位到指标的句柄,是和虚拟机实现相干的
- 次要针对:类、接口、字段、类办法、接口办法、办法类型、办法句柄、调用点限定符
类的初始化
- 类的初始化就是为类的动态变量赋初始值,或者说是执行类结构器 办法的过程
- 初始化一个类的时候,并不会先初始化它实现的接口
- 初始化一个接口的时候,并不会先初始化它的父接口
- 只有当程序首次应用接口外面的变量或者是调用接口办法的时候,才导致接口初始化
- 如果类还没有加载和连贯,就先加载和连贯
- 如果类存在父类,且父类没有初始化,就先初始化父类
- 如果类中存在初始化语句,就顺次执行这些初始化语句
- 如果是接口的话:
- 调用 Classloader 类的 loadClass 办法类装载一个类,并不会初始化这个类,不是对类的被动应用
类的被动初始化
类的初始化机会
- Java 程序对类的应用形式分成:被动应用和被动应用,JVM 必须在每个类或接口”首次被动应用“时才初始化它们;被动应用类不会导致类的初始化,被动应用的状况:
- 创立类实例
- 拜访某个类或接口的动态变量
- 调用类的静态方法
- 反射某个类
- 初始化某个类的子类,而父类还没有初始化
- JVM 启动的时候运行的主类
- 定义了 default 办法的接口,当接口实现类初始化时
类的卸载
- 当代表一个类的 Class 对象不再被援用,那么 Class 对象的生命周期就完结了,对应的在办法区中的数据也会被卸载
- JVM 自带的类加载器装载的类,是不会卸载的,由用户自定义的类加载器的加载的类是能够卸载的
内存调配
在这里插入图片形容
JVM 的简化架构和运行时数据区
在这里插入图片形容
运行时数据区
- PC 寄存器、Java 虚拟机栈、Java 堆、办法区、运行时常量池、本地办法栈等
PC 寄存器
- 每个线程都领有一个 PC 寄存器,是线程公有的,用来存储指向下一条指令的地址
- 在创立线程的时候,创立相应的 PC 寄存器
- 执行本地办法时,PC 寄存器的值为 undefined
- 是一块比拟小的内存空间,是惟一一个在 JVM 标准中没有规定 OutOfMemoryError 的内存区域
Java 栈
- 栈由一系列帧(栈帧)(Frame)组成(因而 Java 栈也叫做帧栈),是线程公有的
- 栈帧用来保留一个办法的局部变量、操作数栈(Java 没有寄存器,所有参数传递应用操作数栈)、常量池指针、动静链接、办法返回等
- 每一次办法调用创立一个帧,并压栈,退出办法的时候,批改栈顶指针就能够把栈帧中的内容销毁
- 局部变量表寄存了编译期可知的各种根本数据类型和援用类型,每个 slot 寄存 32 位的数据,long、double、占两个槽位
- 栈的长处:存取速度比堆块,仅次于寄存器
- 栈的毛病:存在栈中的数据大小、生存区是在编译器决定的,不足灵活性
Java 堆
- 用来寄存利用零碎创立的对象和数组,所有线程共享 Java 堆
- GC 次要治理堆空间,对分代 GC 来说,堆也是分代的
- 堆的长处:运行期动态分配内存大小,主动进行垃圾回收;
- 堆的毛病:效率绝对较慢
办法区
- 办法区是线程共享的,通常用来保留装载的类的构造信息
- 通常和元空间关联在一起,但具体的跟 JVM 实现和版本无关
- JVM 标准把办法区形容为堆的一个逻辑局部,但它有一个别名称为 Non-heap(非堆),应是为了与 Java 堆离开
运行时常量池
- 是 Class 文件中每个类或接口的常量池表,在运行期间的示意模式,通常包含:类的版、字段、办法、接口等信息
- 在办法区中调配
- 通常在加载类和接口到 JVM 后,就创立相应的运行时常量池
本地办法栈
- 在 JVM 中用来反对 native 办法执行的栈就是本地办法栈
栈、堆、办法区交互关系
在这里插入图片形容
Java 堆内存模型和调配
Java 堆内存概述
- Java 堆用来寄存利用零碎创立的对象和数组,所有线程共享 Java 堆
- Java 堆是在运行期动态分配内存大小,主动进行垃圾回收
- Java 垃圾回收(GC)次要就是回收堆内存,对分代 GC 来说,堆也是分代的
Java 堆的构造
在这里插入图片形容
- 新生代用来寄存新调配的对象;新生代中通过垃圾回收,没有回收掉的对象,被复制到老年代
- 老年代存储对象比新生代存储对象的年龄大得多
- 老年代存储一些大对象
- 整个堆大小 = 新生代 + 老年龄
- 新生代 = Eden + 存活区
- 从前的长久代,用来寄存 Class、Method 等元信息的区域,从 JDK8 开始去掉了,取而代之的是元空间(MetaSpace),元空间并不在虚拟机外面,而是间接应用本地内存
对象内存布局
- 对象在内存中贮存的布局(这里以 Hotspot 虚拟机为例来阐明),分为:对象头、实例数据和对齐填充
- 对象头,蕴含两局部:
- Mark Word:存储对象本身的运行数据,如:HashCode、GC 分代年龄,锁状态标记等
- 类型指针:对象指向它的类元数据的指针
- 实例数据:真正寄存对象实例数据的中央
- 对齐填充:这部分不肯定存在,也没有什么特地含意,仅仅是占位符。因为 HotSpot 要求对象起始地址都是 8 字节的整数倍,如果不是,就对齐
对象的拜访定位
- 应用句柄:Java 堆中会划分出一块内存来作为句柄池,reference 中存储句柄的地址,句柄中存储对象的实例数据和类元数据的地址,如图
在这里插入图片形容
- 应用指针:Java 堆中会寄存拜访类元数据的地址,reference 存储的就间接是对象的地址,如图:
在这里插入图片形容
Trace 跟踪和 Java 堆的参数配置
“
doc
Trace 跟踪参数
- 能够打印 GC 的简要信息:-Xlog:gc
- 打印 GC 详细信息:-Xlog:gc*
- 指定 GC log 的地位,以文件输入:-Xlog:gc:garbage-collection.log
- 每一次 GC 后,都打印堆信息:-xlog:gc+heap = debug
GC 日志格局
- GC 产生的工夫,也就是 JVM 从启动以来通过的秒数
- 日志级别信息,和日志类型标记
- GC 辨认号
- GC 的类型和阐明 GC 的起因
- 容量:GC 前容量 -> GC 后容量(该区域总容量)
- GC 持续时间,单位秒。有的收集器会有具体的形容,比方:user 示意应用程序耗费的工夫,sys 示意零碎内核耗费的工夫,real 示意操作从开始到完结的工夫
Java 堆的参数
- -Xms:初始化堆大小,默认物理内存的 1/64
- -Xmx:最大堆大小,默认物理内存的 1/4
- -Xmn:新生代大小,默认整个堆的 3/8
- -XX:+HeapDumpOnOutOfMemoryError:OOM 时导出堆到文件
- -XX:+HeapDumpPath:导出 OOM 的门路
- -XX:NewRatio:老年代与新生代的比值,如果 xms=xmx,且设置了 xmn 的状况,该参数不必设置
- -XX:SurvivorRatio:Eden 区和 Survivor 区的大小比值,设置为 8,则两个 Survivor 区与一个 Eden 区的比值为 2:8,一个 Survivor 占整个新生的 1/10
- -XX:OnOutOfMemoryError:在 OOM 时,执行一个脚本
- -Xss:通常只有几百 k,决定了函数调用的深度
元空间的参数
- -XX:MetaspaceSize:初始空间大小
- -XX:MaxMetaspaceSize:最大空间,默认是没有限度的
- -XX:MinMetaspaceFreeRatio:在 GC 之后,最小的 Metaspace 残余空间容量的百分比
- -XX:MaxMetaspaceFreeRatio:在 GC 之后,最大的 Metaspace 残余空间容量的百分比
字节码执行引擎
- JVM 的字节码执行引擎,性能根本就是输出字节码文件,而后对字节码进行解析并解决,最初输入执行的后果
- 实现形式可能有通过解释器间接解释执行字节码,或者通过即时编译器产生本地代码,也就是编译执行,当然也可能两者都有
栈帧
- 栈帧是用于反对 JVM 进行办法调用和办法执行的数据结构
- 栈帧随着办法调用而创立,随着办法完结而销毁
- 栈帧外面存储了办法的局部变量表、操作数栈、动静链接、办法返回地址等信息
局部变量表
- 局部变量表:用来寄存办法参数和办法外部定义的局部变量的存储空间
- 以变量槽 slot 为单位,目前一个 slot 寄存 32 位以内的数据类型
- 对于 64 位的数据占 2 个 slot
- 对于实例办法,第 0 位 slot 寄存的是 this,而后从 1 到 n,顺次调配给参数列表
- 而后依据办法体外部定义的变量程序和作用域来调配 slot
- slot 是复用的,以节俭栈帧的空间,这种设计可能会影响零碎的垃圾收集行为
操作数栈
- 操作数栈:用来寄存办法运行期间,各个指令操作的数据
- 操作数栈中元素的数据类型必须和字节码指令的程序严格匹配
- 虚拟机在实现栈帧的时候可能会做一些优化,让两个栈帧呈现局部重叠区域,以寄存专用的数据
动静链接
- 动静链接:每一个栈帧持有一个指向运行时常量池中该栈帧所属办法的援用,以反对办法调用过程的动静链接
- 动态解析:类加载的时候,符号援用就转化为间接援用
- 动静链接:运行期间转化为间接援用
办法返回地址
- 办法返回地址:办法执行后返回的地址
办法调用
- 办法调用:就是确定具体调用哪一个办法,并不波及办法外部的执行过程
- 局部办法是间接在类加载的解析阶段,就确定了间接援用关系
- 然而对于实例办法,也称虚办法,因为多重和多态,须要运行期动静分派
分派
- 动态分派:所有依赖动态类型来定位办法执行版本的分派形式,比方:重载办法
- 动静分派:依据运行期的理论类型来定位办法执行版本的分派形式,比方:笼罩办法
- 单分派和多分派:就是依照分派思考的维度,多于一个的就算多调配,只有一个的称为单分派
垃圾回收
垃圾回收概述
- 什么是垃圾:简略说就是内存中曾经不再被应用到的内存空间就是垃圾
- 垃圾回收算法:
- 可作为 GC Roots 的对象包含:虚拟机栈(栈帧局部变量)中援用的对象、办法区类动态属性援用的对象、办法区中常量援用的对象、本地办法栈中 JNI 援用的对象
- HotSpot 应用了一组叫做 OopMap 的数据结构达到精确式 GC 的目标
- 在 OopMap 的帮助下,JVM 能够很快的做完 GC Roots 枚举。然而 JVM 并没有为每一条指令生成一个 OopMap
- 记录 OopMap 的这些“特定地位”被称为平安点,即以后线程执行到平安点后才容许暂停进行 GC
- 如果一段代码中,对象援用关系不会发生变化,这个区域中任何中央开始 GC 都是平安的,那么这个区域称为平安区域
- 长处:生效简略、效率高
- 毛病:不能解决对象之间循环援用的问题
- 援用计数法:给对象增加一个援用计数器,有拜访就加 1,援用生效就减 1
- 根搜索算法(可达性分析法):从根(GC Roots)节点向下搜寻对象节点,搜寻走过的门路称为援用链,当一个对象到根之间没有连通的话,则该对象不可用
垃圾回收根底
跨代援用
- 跨代援用:也就是一个代中的对象援用另一个代中的对象
- 跨代援用假说:跨代援用绝对于同代援用来说只是极少数
- 隐含推论:存在互相援用关系的两个对象,是应该偏向于同时生存或同时沦亡的
记忆集
- 记忆集(Remembered Set):一种用于记录从非收集区域指向收集区域的指针汇合的形象数据结构
- 字长精度:每个记录准确到一个机器字长,该子蕴含跨代指针
- 对象精度:每个记录准确到一个对象,该对象里有字段含有跨代指针
- 卡精度:每个记录准确到一块内存区域,该区域内有对象含有跨代指针
- 卡表(Card Table):是记忆集的一种具体实现,定义了记忆集的记录精度和与堆内存的映射关系等
- 卡表的每个元素都对应着其标识的内存区域中一块特定大小的内存块,这个内存块称为 卡页(Card Page)
写屏障
- 写屏障能够看成是 JVM 对”援用类型字段赋值“这个动作的 AOP
- 通过写屏障来实现当对象状态扭转后,保护卡表状态
判断是否垃圾的步骤
- 跟搜索算法判断不可用
- 看是否有必要执行 finalize 办法
- 两个步骤走完后对象依然没有人应用,那就属于垃圾
GC 类型
- MinorGC / YoungGC:产生在新生代的收集动作
- MajorGC / OldGC:产生在老年代的 GC,目前只有 CMS 收集器会有独自收集老年代的行为
- MixedGC:收集整个新生代以及局部老年代,目前只有 G1 收集器会有这种行为
- FullGC:收集整个 Java 堆和办法区的 GC
Stop-The-World
- STW 是 Java 中一种全局暂停的景象,多半因为 GC 引起。所谓全局进展,就是所有 Java 代码进行运行,native 代码能够执行,但不能和 JVM 交互
- 其危害是长时间服务进行,没有响应;对于 HA 零碎,可能引起主备切换,严重危害生产环境
垃圾收集类型
- 串行收集:GC 单线程内存回收、会暂停所有的用户线程,如:Serial
- 并行收集:多个 GC 线程并发工作,此时用户线程是暂停的,如:Parallel
- 并发收集:用户线程和 GC 线程同时执行(不肯定是并行,可能交替执行),不须要进展用户线程,如:CMS
判断类无用的条件
- JVM 中该类的所有实例都曾经被回收
- 加载该类的 ClassLoader 曾经被回收
- 没有任何中央援用该类的 Class 对象
- 无奈在任何中央通过反射拜访这个类
垃圾回收算法
标记革除算法
- 标记革除算法(Mark-Sweep):分为标记和革除两个阶段,先标记出要回收的对象,而后对立回收这些对象
在这里插入图片形容
- 长处:简略
- 毛病:
- 效率不高,标记和革除的效率都不高
- 产生大量不间断的内存碎片,从而导致在调配大对象时触发 GC
复制算法
- 复制算法(Copying):把内存分成两块完全相同的区域,每次应用其中一块,当一块应用完了,就把这块上还存活的对象拷贝到另外一块,而后把这块革除掉
在这里插入图片形容
- 长处:实现简略,运行高效,不必思考内存碎片问题
- 毛病:内存有些节约
- JVM 理论实现中,是将内存分为一块较大的 Eden 区和两块较小的 Survivor 空间,每次应用 Eden 和一块 Survivor,回收时,把存活的对象复制到另一块 Survivor
- HotSpot 默认的 Eden 和 Survivor 比是 8:1,也就是每次能用 90% 的新生代空间
如果 Survivor 空间不够,就要依赖老年代进行调配担保,把放不下的对象间接进入老年代
“
调配担保:当新生代进行垃圾回收后,新生代的存活区搁置不下,那么须要把这些对象搁置到老年代去的策略,也就是老年代为新生代的 GC 做空间调配担保,步骤如下:
- 在产生 MinorGC 前,JVM 会查看老年代的最大可用的间断空间,是否大于新生代所有对象的总空间,如果大于,能够确保 MinorGC 是平安的
- 如果小于,那么 JVM 会查看是否设置了容许担保失败,如果容许,则持续查看老年代最大可用的间断空间,是否大于历次降职到老年代对象的均匀大小
- 如果大于,则尝试进行一次 MinorGC
- 如果不大于,则改做一次 Full GC
标记整顿算法
- 标记整顿算法 (Mark-Compact):因为复制算法在存活对象比拟多的时候,效率较低,且有空间节约,因而老年代个别不会选用复制算法,老年代多选用标记整顿算法
- 标记过程跟标记革除算法一样,但后续不是间接革除可回收对象,而是让所有存活对象都向一端挪动,而后间接革除边界以外的内存
在这里插入图片形容
垃圾收集器
- 串行收集器、并行收集器、新生代 Parallel、Scavenge 收集器、CMS、G1
在这里插入图片形容
串行收集器
- Serial(串行)收集器 / Serial Old 收集器,是一个单线程的收集器,在垃圾收集时,会 Stop-the-World
在这里插入图片形容
- 长处:简略,对于单 cpu,因为没有多线程的交互开销,可能更高效,是默认的 Client 模式下的新生代收集器
- 应用 -XX:+UseSerialGC 来开启,会应用:Serial + SerialOld 的收集器组合
- 新生代应用复制算法,老年代应用标记 - 整顿算法
并行收集器
ParNew 收集器
- ParNew(并行)收集器:应用多线程进行垃圾回收,在垃圾收集时,会 Stop-the-World
在这里插入图片形容
- 在并发能力好的 CPU 环境里,它进展的工夫要比串行收集器短;但对于单 CPU 或并发能力较弱的 CPU,因为多线程的交互开销,可能比串行回收器更差
- 是 Server 模式下首选的新生代收集器,且能和 CMS 收集器配合应用
- 不再应用 -XX:+UseParNewGC 来独自开启
- -XX:ParallelGCThreads:指定线程数,最好与 cpu 数量统一
新生代 Parallel Scavenge 收集器
- 新生代 Parallel Scavenge 收集器 / Parallel Old 收集器:是一个利用于新生代的,应用复制算法的、并行的收集器
- 与 ParNew 很相似,但更关注吞吐量,能最高效率的利用 CPU,适宜运行后盾利用
在这里插入图片形容
- 应用 -XX:+UseParallelGC 来开启
- 应用 -XX:+UseParallelOldGC 来开启老年代应用 ParallelOld 收集器,应用 Parallel Scavenge + Parallel Old 的收集器组合
- -XX:MaxGCPauseMillis:设置 GC 的最大进展工夫
- 新生代应用复制算法,老年代应用标记 - 整顿算法
CMS 收集器
- CMS(Concurrent Mark and Sweep 并发标记革除)收集器分为:初始标记:只标记 GC Roots 能间接关联到的对象;并发标记:进行 GC Roots Tracing 的过程
- 从新标记:修改并发标记期间,因程序运行导致标记发生变化的那一部分对象
- 并发革除:并发回收垃圾对象
在这里插入图片形容
- 在初始化标记和从新标记两个阶段还是会产生 Stop-the-World
- 应用标记革除算法,多线程并发收集的垃圾收集器
- 最初的重置线程,指的是清空跟收集相干的数据并重置,为下次收集做筹备
- 长处:低进展,并发执行
- 毛病:
- 并发执行,对 CPU 资源压力大
- 无奈解决 在处理过程中 产生的垃圾(浮动垃圾),可能导致 FullGC
- 采纳的标记革除算法会导致大量碎片,从而在调配大对象可能触发 FullGC
- 开启:-XX:UseConcMarkSweepGC:应用 ParNew + CMS + Serial Old 的收集器组合,Serial Old 将作为 CMS 出错的后备收集器
- -XX:CMSInitiatingOccupancyFraction:设置 CMS 收集器在老年代空间被应用多少后触发回收,默认 80%
G1 收集器
- G1(Garbage-First)收集器:是一款面向服务利用的收集器,与其余收集器相比,具备以下特点:
- G1 把内存划分成多个独立的区域(Region)
- G1 仍采纳分代思维,保留了新生代和老年代,但它们不再是物理隔离的,而是一部分 Region 的汇合,且不须要 Region 是间断的
在这里插入图片形容
- G1 能充分利用多 CPU、多核环境硬件劣势,尽量缩短 STW
- G1 整体上采纳标记 - 整顿算法,部分是通过复制算法,不会产生内存碎片
- G1 的进展可预测,能明确指定在一个时间段内,耗费在垃圾收集上的工夫不能超过多长时间
- G1 跟踪各个 Region 外面垃圾堆的价值大小,在后盾保护一个优先列表,每次依据容许的工夫来回收价值最大的区域,从而保障在无限工夫内的高效收集
- 垃圾收集:
- 初始标记:只标记 GC Roots 能间接关联到的对象
- 并发标记:进行 GC Roots Tracing 的过程
- 最终标记:修改并发标记期间,因程序运行导致标记发生变化的那一部分对象
- 筛选回收:依据工夫来进行价值最大化的回收
在这里插入图片形容
- 应用和配置 G1:-XX:+UseG1GC:开启 G1,默认就是 G1
- -XX:MaxGCPauseMillis = n:最大 GC 进展工夫,这是个软指标,JVM 将尽可能(但不保障)进展小于这个工夫
- -XX:InitiatingHeapOccupancyPercent = n:堆占用了多少的时候就触发 GC,默认为 45
- -XX:NewRatio = n:默认为 2
- -XX:SurvivorRatio = n:默认为 8
- -XX:MaxTenuringThreshold = n:新生代到老年代岁数,默认是 15
- -XX:ParallelGCThreads = n:并行 GC 的线程数,默认值会依据平台不同而不同
- -XX:ConcGCThreads = n:并发 GC 应用的线程数
- -XX:G1ReservePercent = n:设置作为闲暇空间的预留内存百分比,以升高指标空间溢出的危险,默认值是 10%
- -XX:G1HeapRegionSize = n:设置的 G1 区域的大小。值是 2 的幂,范畴是 1MB 到 32MB,指标是依据最小的 Java 堆大小划分出约 2048 个区域
ZGC 收集器(理解)
- ZGC 收集器:JDK11 退出的具备试验性质的低提早收集器
- ZGC 的设计指标是:反对 TB 级内存容量,暂停工夫低(<10ms),对整个程序吞吐量的影响小于 15%
- ZGC 外面的新技术:着色指针 和 读屏障
- GC 性能指标:
- 吞吐量 = 利用代码执行的工夫 / 运行的总工夫
- GC 负荷,与吞吐量相同,是 GC 工夫 / 运行的总工夫
- 暂停工夫,就是产生 Stop-the-World 的总工夫
- GC 频率,就是 GC 在一个时间段产生的次数
- 反应速度:就是从对象成为垃圾开始到被回收的工夫
- 交互式利用通常心愿暂停工夫越少越好
- JVM 内存配置准则:
- 新生代尽可能设置大点,如果太小会导致:
- 对于老年代,针对响应工夫优先的利用:因为老年代通常采纳并发收集器,因而其大小要综合思考并发量和并发持续时间等参数
- 对于老年代,针对吞吐量优先的利用:通常设置较大的新生代和较小的老年代,这样能够尽可能回收大部分短期对象,缩小中期对象,而老年代尽量寄存长期存活的对象
- 根据对象的存活周期进行分类,对象优先在新生代调配,长时间存活的对象进入老年代
- 依据不同代的特点,选取适合的收集算法:大量对象存活,适宜复制算法;大量对象存活,适宜标记革除或标记整顿
- 如果设置小了,可能会造成内存碎片,高回收频率会导致利用暂停
- 如果设置大了,会须要较长的回收工夫
- YGC 次数更加频繁
- 可能导致 YGC 后的对象进入老年代,如果此时老年代满了,会触发 FGC
高效并发
Java 内存模型和内存间的交互操作
Java 内存模型
- JCP 定义了一种 Java 内存模型,以前是在 JVM 标准中,起初独立进去成为 JSR-133(Java 内存模型和线程标准订正)
- 内存模型:在特定的操作协定下,对特定的内存或高速缓存进行读写访问的过程形象
- Java 内存模型次要关注 JVM 中把变量值存储到内存和从内存中取出变量值这样的底层细节
在这里插入图片形容
- 所有变量(共享的)都存储在主内存中,每个线程都有本人的工作内存;工作内存中保留该线程应用到的变量的主内存正本拷贝
- 线程对变量的所有操作(读、写)都应该在工作内存中实现
- 不同线程不能互相拜访工作内存,交互数据要通过主内存
内存间的交互操作
- Java 内存模型规定了一些操作来实现内存间交互,JVM 会保留它们是原子的
- lock:锁定,把变量标识为线程独占,作用于主内存变量
- unlock:解锁,把锁定的变量开释,别的线程能力应用,作用于主内存变量
- read:读取,把变量从主内存读取到工作内存
- load:载入,把 read 读取到的值放入工作内存的变量正本中
- use:应用,把工作内存中一个变量的值传递给执行引擎
- assign:赋值,把从执行引擎接管到的值赋给工作内存外面的变量
- store:存储,把工作内存中一个变量的值传递到主内存中
- wirte:写入,把 store 进来的数据寄存如主内存的变量中
在这里插入图片形容
内存间的交互操作的规定
- 不容许 read 和 load、store 和 write 操作之一独自呈现,以上两个操作必须依照程序执行,但不保障间断执行,也就是说,read 和 load 之间、store 与 write 之间是可插入其余指令的
- 不容许一个线程抛弃它的最近的 assign 操作,即变量在工作内存中扭转了之后必须把该变动同步回主内存
- 不容许一个线程无起因地(没有产生过任何 assign 操作)把数据从线程的工作内存同步回主内存中
- 一个新的变量只能从主内存中”诞生“,不容许在工作内存中间接应用一个未被初始化的变量,也就是对一个变量施行 use 和 store 操作之前,必须先执行过了 assign 和 load 操作
- 一个变量在同一个时刻只容许一条线程对其执行 lock 操作,但 lock 操作能够被同一条线程反复执行屡次,屡次执行 lock 后,只有执行雷同次数的 unlock 操作,变量才会被解锁
- 如果对一个变量执行 lock 操作,将会清空工作内存中此变量的值,在执行引擎应用这个变量前,须要从新执行 load 或 assign 操作初始化变量的值
- 如果一个变量没有被 lock 操作锁定,则不容许对它执行 unlock 操作,也不能 unlock 一个被其余线程锁定的变量
- 对一个变量执行 unlock 操作之前,必须先把此变量同步回主内存(执行 store 和 write 操作)
volatile 个性
多线程中的可见性
- 可见性:就是一个线程批改了变量,其余线程能够晓得
- 保障可见性的常见办法:volatile、synchronized、final(一旦初始化实现,其余线程就可见)
volatile
- volatile 基本上是 JVM 提供的最轻量级的同步机制,用 volatile 润饰的变量,对所有的线程可见,即对 volatile 变量所做的写操作能立刻反映到其余线程中
- 用 volatile 润饰的变量,在多线程环境下依然是不平安的
- volatile 润饰的变量,是禁止指令重排优化的
- 适宜应用 valatile 的场景
- 运算后果不依赖变量的以后值
- 确保只有一个线程批改变量的值
指令重排
- 指令重排:指的是 JVM 为了优化,在条件容许的状况下,对指令进行肯定的重新排列,间接运行以后可能立刻执行的后序指令,避开获取下一条指令所需数据造成的期待
- 线程内串行语义,不思考多线程间的语义
- 不是所有的指令都能重排,比方:
- 写后读 a = 1;b = a;写一个变量之后,再读这个地位
- 写后写 a = 1;a = 2;写一个变量之后,再写这个变量
- 读后写 a = b;b = 1;读一个变量之后,再写这个变量
- 以上语句不可重排,然而 a = 1;b = 2;是能够重排的
- 程序程序准则:一个线程内保障语义的串行性
- volatile 规定:volatile 变量的写,先产生于读
- 锁规定:解锁(unlock)必然产生在随后的加锁(lock)前
- 传递性:A 先于 B,B 先于 C,那么 A 必然先于 C
- 线程的 start 办法先于它的每一个动作
- 线程的所有操作先于线程的终结
- 线程中断(interrupt())先于被中断线程的代码
- 对象的构造函数执行完结先于 finalize() 办法
Java 线程平安的解决办法
- 不可变是线程平安的
- 互斥同步(阻塞同步):synchronized、java.util.concurrent.ReentrantLock。目前这两个办法性能曾经差不多了,倡议优先选用 synchronized,ReentrantLock 减少了如下个性:
- 期待可中断:当持有锁的线程长时间不开释锁,正在期待的线程能够抉择放弃期待
- 偏心锁:多个线程期待同一个锁时,须严格依照申请锁的工夫程序来获取锁
- 锁绑定多个条件:一个 ReentrantLock 对象能够绑定多个 condition 对象,而 synchronized 是针对一个条件的,如果要多个,就得有多个锁
- 非阻塞同步:是一种基于抵触查看的乐观锁策略,通常是先操作,如果没有抵触,操作就胜利了,有抵触再采取其余形式进行弥补解决
- 无同步计划:其实就是在多线程中,办法并不波及共享数据,天然也就无需同步了
锁优化
自旋锁与自适应自旋
- 自旋:如果线程能够很快取得锁,那么能够不再 OS 层挂起线程,而是让线程做几个忙循环,这就是自旋
- 自适应自旋:自旋的工夫不再固定,而是由前一次在同一个锁上的自旋工夫和锁的拥有者状态来决定
- 如果锁被占用工夫很短,自旋胜利,那么能节俭线程挂起、以及切换工夫,从而晋升零碎性能
- 如果锁被占用工夫很长,自旋失败,会白白浪费处理器资源,升高零碎性能
锁打消
- 在编译代码的时候,检测到基本不存在共享数据竞争,天然也就无需同步加锁了;通过 -XX:+EliminateLocks 来开启
同时要应用 -XX:DoEscapeAnalysis 开启逃逸剖析
“
逃逸剖析:
- 如果一个办法中定义的一个对象,可能被内部办法援用,称为办法逃逸
- 如果对象可能被其余内部线程拜访,称为线程逃逸,比方赋值给类变量或者能够在其余线程中拜访的实例变量
锁粗化
- 通常咱们都要求同步块要小,但一系列间断的操作导致一个对象重复的加锁和解锁,这会导致不必要的性能损耗。这种状况倡议把锁同步的范畴加大到整个操作序列
轻量级锁
- 轻量级是绝对于传统锁机制而言,本意是没有多线程竞争的状况下,缩小传统锁机制应用 OS 实现互斥所产生的性能损耗
- 其实现原理很简略,就是相似乐观锁的形式
- 如果轻量级锁失败,示意存在竞争,降级为重量级锁,导致性能降落
偏差锁
- 偏差锁是在无竞争状况下,间接把整个同步打消了,连乐观锁都不必,从而进步性能;所谓的偏差,就是偏心,即锁会偏差于以后曾经占有锁的线程
- 只有没有竞争,取得偏差锁的线程,在未来进入同步块,也不须要做同步
- 当有其余线程申请雷同的锁时,偏差模式完结
- 如果程序中大多数锁总是被多个线程拜访的时候,也就是竞争比拟强烈,偏差锁反而会升高性能
- 应用 -XX:-UseBiasedLocking 来禁用偏差锁,默认开启
JVM 中获取锁的步骤
- 会先尝试偏差锁;而后尝试轻量级锁
- 再而后尝试自旋锁
- 最初尝试一般锁,应用 OS 互斥量在操作系统层挂起
同步代码的根本规定
- 尽量减少持有锁的工夫
- 尽量减少锁的粒度
性能监控与故障解决工具
命令行工具
- 命令行工具:jps、jinfo、jstack、jmap、jstat、jstatd、jcmd
- 图形化工具:jconsole、jmc、visualvm
- 两种连贯形式:JMX、jstatd
JVM 检测工具的作用
- 对 jvm 运行期间的外部状况进行监控,比方:对 jvm 参数、CPU、内存、堆等信息的查看
- 辅助进行性能调优
- 辅助解决利用运行时的一些问题,比方:OutOfMemoryError、内存透露、线程死锁、锁争用、Java 过程耗费 CPU 过高 等等
jps
- jps(JVM Process Status Tool):次要用来输入 JVM 中运行的过程状态信息,语法格局如下:jps [options] [hostid]
- hostid 字符串的语法与 URI 的语法基本一致:[protocol:] [[ //] hostname] [:port] [/servername],如果不指定 hostid,默认为以后主机或服务器
jinfo
- 打印给定过程或外围或近程调试服务器的配置信息。语法格局:jinfo [option] pid #指定过程号(pid)的过程
- jinfo [option] <executable #指定外围文件
- jinfo [option] [server-id@] #指定近程调试服务器
jstack
- jstack 次要用来查看某个 Java 过程内的线程堆栈信息。语法格局如下:jstack [option] pid
- jstack [option] executable core
- jstack [option] [server-id@] remorte-hostname-or-ip
jmap
- jmap 用来查看堆内存应用状况,语法格局如下:jmap [option] pid
- jmap [option] executable core
- jmap [option] [server-id@] remote-hostname-or-ip
jstat
- JVM 统计监测工具,查看各个区域内存和 GC 的状况
- 语法格局如下:jstat [generalOption | outputOptions vmid [interval[s|ms] [count]]]
jstated
- 虚拟机的 jstat 守护进行,次要用于监控 JVM 的创立与终止,并提供一个接口,以有序近程监督工具附加到本地零碎上运行的 JVM、
- 语法格局:jstatd [options]
jcmd
- JVM 诊断工具,将诊断命令申请发送到正在运行的额 Java 虚拟机,比方能够用来导出堆,查看 java 过程,导出线程信息,执行 GC 等
图形化工具
jconsole
- 一个用于监督 Java 虚拟机的合乎 JMX 的图形工具。它能够监督本地和近程 JVM,还能够监督和管理应用程序
jmc
- jmc(JDK Mission Control)Java 工作管制(JMC)客户端包含用于监督和治理 Java 应用程序的工具,而不是引入通常与这些类型的工具相关联的性能开销
VisualVM
- 一个图形工具,它提供无关在 Java 虚拟机中运行的基于 Java 技术的应用程序的详细信息
- Java VisualVM 提供内存和 CPU 剖析,堆转储剖析,内存透露检测,拜访 MBean 和垃圾回收
近程连贯
- JMX 连贯能够查看:零碎信息、CPU 应用状况、线程多少、手动执行垃圾回收等比拟偏于零碎层面的信息
- jstatd 连贯形式能够提供:JVM 内存散布详细信息、垃圾回收分布图、线程详细信息,甚至能够看到某个对象应用内存的大小
s|ms] [count]]]
正文完