导图社区 二:JVM性能调优
包含了jvm相关全部知识点,好好备考
编辑于2020-06-27 00:27:13二:JVM性能调优
2.JVM中对象
JVM中的对象
根据new的参数是否能在常量池中 定位到一个类的符号引用
1.检查加载
执行相应的类加载过程
2.分配内存
根据方法区的信息 确定为该类分配的内存空间大小
a.指针碰撞
java堆内存空间规整的情况下
把那个指针向空闲空间那边 挪动一段与对象大小相等的距离
b.空闲列表
java堆空间不规整的情况下
并发安全
解决:CAS机制
分配缓冲
每个线程在Java堆中预先分配一小块私有内存
如果设置了虚拟机参数 -XX:+UseTLAB 本地线程分配缓冲(Thread Local Allocation Buffer,TLAB)
3.内存空间初始化
将分配到的内存空间都初始化为零
对象的实例字段在Java代码中可以不赋初始值就直接使用
4.设置
对象头设置
这个对象是哪个类的实例、 如何才能找到类的元数据信息 对象的哈希码、 对象的GC分代年龄等信息
5.对象初始化
从Java程序的视角来看,对象创建才刚刚开始
构造函数和变量赋值
对象的内存布局
对象头(Header)
存储对象自身的运行时数据:8Byte
哈希码(HashCode)、 GC标志、 对象分代年龄、 锁状态标志、 线程持有的锁、 偏向线程ID、 偏向时间戳
类型指针-8Byte
指针压缩!--4Byte
确定这个对象是哪个类的实例
数组长度-4Byte-非必须
对齐填充
说明:当包含数组类型-且开启指针压缩,会有两个padding的情况
实例数据(Instance Data)
对齐填充(Padding)
对象的访问定位
直接指针
reference中存储的直接就是对象地址
优势:速度更快,它节省了一次指针定位的时间开销
Sun HotSpot-默认使用!!
句柄
Java堆中将会划分出一块内存来作为句柄池
句柄中包含了对象实例数据与类型数据各自的具体地址
优势:在对象被移动(垃圾收集时移动对象是非常普遍的行为)时 只会改变句柄中的实例数据指针,而reference本身不需要修改
堆内存分配策略
优先在Eden区分配
新生代:Eden区 Survivor(from)区:设置Survivor是为了减少送到老年代的对象 Survivor(to)区:设置两个Survivor区是为了解决碎片化的问题(复制回收算法)
-XX:+PrintGCDetails 打印垃圾回收日志
-Xmn10m 新生代空间10m
大对象直接进入老年代
-XX:PretenureSizeThreshold=4m 超过多少大小的对象直接进入老年代
-XX:+UseSerialGC
PretenureSizeThreshold参数 只对Serial和ParNew两款收集器有效
最典型的大对象是那种很长的字符串以及数组
1.避免大量内存复制, 2.避免提前进行垃圾回收
原理:大对象一般生存时间较长
长期存活的对象进入老年代
年龄增加到一定程度(默认为15)_时, 就会被晋升到老年代
根据年龄动态判定
如果在 Survivor空间中相同年龄所有对象大小的综合大于Survivor空间的一半, 年龄大于或等于该年龄的对象就可以直接进入老年代
空间分配担保
Minor GC之前,虚拟机会先检查 老年代最大可用的连续空间是否大于新生代所有对象总空间
成立,那么Minor GC可以确保是安全的
不成立,则虚拟机会查看HandlePromotionFailure设置值是否允许担保失败
如果允许,那么会继续检查老年代最大可用的连续空间 是否大于历次晋升到老年代对象的平均大小
如果大于,将尝试着进行一次Minor GC
。。。
否则:进行一次Full GC
HotSpot默认是开启空间分配担保的
java中的泛型
什么是泛型
即“参数化类型”
本质是为了参数化类型(在不创建新的类型的情况下,通过泛型指定的不同类型来控制形参具体限制的类型)
引入一个类型变量T(其他大写字母都可以,不过常用的就是T,E,K,V等等),并且用<>括起来,并放在类名的后面
泛型类、泛型接口、泛型方法
why-need
适用于多种数据类型执行相同的代码
泛型中的类型在使用时指定,不需要强制类型转换
jvm-how-实现
只在程序源码中存在, 在编译后的字节码文件中,就已经替换为原来的原生类型 (Raw Type,也称为裸类型)了, 并且在相应的地方插入了强制转型代码
对于运行期的Java语言来说, ArrayList<int>与ArrayList<String>就是同一个类
实际上是Java语言的一颗语法糖
泛型实现方法称为类型擦除, 基于这种方法实现的泛型称为伪泛型
擦除法所谓的擦除,仅仅是对方法的Code属性中的字节码进行擦除, 实际上元数据中还是保留了泛型信息, 这也是我们能通过反射手段取得参数化类型的根本依据
4.JVM执行子系统
Class文件结构
jvm无关性
一次编写,到处运行
字节码(ByteCode)是构成平台无关性的基石
Class类文件
Class文件是一组以8位字节为基础单位的二进制流
8字节,对齐-压缩!
Class文件都对应着唯一一个类或接口的定义信息
不一定以磁盘文件的形式存在
动态代理
javassist-运行时编译
文件格式
各个数据项目严格按照顺序紧凑地排列
中间没有添加任何分隔符
两种数据类型:无符号数和表
无符号数属于基本的数据类型,u1、u2、u4、u8
表是由多个无符号数或者其他表作为数据项构成的复合数据类型
表用于描述有层次关系的复合结构的数据,整个Class文件本质上就是一张表
文件格式详解
魔数与Class文件的版本
头4个字节-p
确定这个文件是否为一个能被虚拟机接受的Class文件
后4个字节存储的是Class文件的版本号
第5和第6个字节是次版本号(MinorVersion)
第7和第8个字节是主版本号(Major Version)
JDK1.8-p
常量池
放置一项u2类型的数据, 代表常量池容量计数值(constant_pool_count)
容量计数是从1而不是0开始
两大类常量:字面量(Literal)和符号引用(Symbolic References)
字面量比较接近于Java语言层面的常量概念, 如文本字符串、声明为final的常量值等
符号引用则属于编译原理方面的概念
类和接口的全限定名(Fully Qualified Name)、 字段的名称和描述符(Descriptor)、 方法的名称和描述符
访问标志
这个Class是类还是接口; 是否定义为public类型; 是否定义为abstract类型; 如果是类的话,是否被声明为final
(父)类索引-接口索引集合
目标:确定这个类的继承关系
类索引用于确定这个类的全限定名
父类索引只有一个
除了java.lang.Object之外,所有的Java类都有父类
接口索引集合就用来描述这个类实现了哪些接口
字段表集合
描述接口或者类中声明的变量
字段(field)包括类级变量以及实例级变量
方法表集合
描述了方法的定义
编译器添加;最典型的便是类构造器“<clinit>”方法和实例构造器“<init>
属性表集合
存储Class文件、字段表、方法表等自己的属性表集合
用于描述某些场景专有的信息。如方法的代码就存储在Code属性表中
字节码指令
由一个字节长度的、代表着某种特定操作含义的数字(称为操作码,Opcode) 以及跟随其后的零至多个代表此操作所需参数(称为操作数,Operands)而构成
操作码的长度为一个字节
so:操作码总数不可能超过256条
大多数的指令都包含了其操作所对应的数据类型信息
大多数对于boolean、byte、short和char类型数据的操作, 实际上都是使用相应的int类型作为运算类型
1.加载和存储指令
将一个局部变量加载到操作栈
iload、iload_<n>、 lload、lload_<n>、 fload、fload_<n>、 dload、dload_<n>、 aload、aload_<n>
将一个数值从操作数栈存储到局部变量表
istore、istore_<n> ...
将一个常量加载到操作数栈:
bipush、sipush
ldc、ldc_w、ldc2_w、 aconst_null、iconst_m1、 iconst_<i>、lconst_<l>、 fconst_<f>、dconst_<d>
扩充局部变量表的访问索引的指令:wide
2.运算或算术指令
用于对两个操作数栈上的值进行某种特定运算, 并把结果重新存入到操作栈顶
加法指令:iadd、ladd、fadd、dadd。 减法指令:isub、lsub、fsub、dsub。 乘法指令:imul、lmul、fmul、dmu
3.类型转换指令
宽化类型转换
int类型到long、float或者double类型。 long类型到float、double类型。 float类型到double类型
窄化类型转换
i2b、i2c、i2s、l2i、 f2i、f2l、d2i、d2l和d2f
4.创建类实例的指令
new!!!
5.创建数组的指令
newarray、anewarray、multianewarray
6.访问字段指令
getfield、putfield、getstatic、putstatic。
7.数组存取相关指令
baload、caload、saload、iaload、laload、faload、daload、aaload。
bastore、castore、sastore、iastore、fastore、dastore、aastore。
取数组长度的指令:arraylength。
8.检查类实例类型的指令
instanceof、checkcast。
9.操作数栈管理指令
将操作数栈的栈顶一个或两个元素出栈:pop、pop2
复制并将复制值重新压入栈顶:dup、dup2、dup_x1、dup2_x1、dup_x2、dup2_x2
将栈最顶端的两个数值互换:swap
10.控制转移指令
有条件或无条件地修改PC寄存器的值
条件分支:
ifeq、iflt、ifle、ifne、ifgt、ifge、 ifnull、ifnonnull、 if_icmpeq、if_icmpne、if_icmplt、 if_icmpgt、if_icmple、if_icmpge、 if_acmpeq和if_acmpne。
复合条件分支:tableswitch、lookupswitch
无条件分支:goto、goto_w、jsr、jsr_w、ret
11.方法调用指令
invokevirtual指令用于调用对象的实例方法
invokeinterface指令用于调用接口方法
invokespecial指令用于调用一些需要特殊处理的实例方法
invokestatic指令用于调用类方法(static方法)
invokedynamic指令用于在运行时动态解析出调用点限定符所引用的方法 并执行该方法
12.方法返回指令
ireturn(当返回值是boolean、byte、char、short和int类型时使用)
lreturn、freturn、dreturn和areturn
另外还有一条return指令供声明为void的方法、实例初始化方法以及类和接口的类初始化方法使用
13.异常处理指令
athrow指令
14.同步指令
有monitorenter和monitorexit两条指令 来支持synchronized关键字的语义
栈帧详解
栈顶-栈帧称为“当前栈帧”, 与这个栈帧相关联的方法称为“当前方法”
1.局部变量表
局部变量表的容量以变量槽(Variable Slot,下称 Slot)为最小单位
boolean、byte、char、short、int、float、double、long 8 种数据类型和 引用reference
64 位的数据类型只有 long 和 double 两种
reference 类型则可能是 32 位也可能是 64 位
2.操作数栈
先进后出(FirstIn Last Out,FILO)栈
解释执行引擎称为“基于栈的执行引擎”
3.动态链接
符号引用是一个地址位置的代号
用代号com/enjoy/pojo/User.Say:()V指代某个类的方法
静态解析:符号引用在类加载阶段或者第一次使用的时候就直接转换成直接引用
动态连接:符号引用在每次运行期间转换为直接引用,即每次运行都重新转换
4.返回地址
正常退出与异常退出
恢复现场
恢复上次方法的局部变量表、操作数栈
把当前方法的返回值,压入调用者栈帧的操作数栈中
用当前栈帧保存的返回地址调整PC计数器的值
5.附加信息
调试相关的信息
数据重叠优化
下面栈帧的一部分操作数栈与上面栈帧的部分局部变量表重叠在一起
基于栈的字节码解释执行引擎
区别于:基于寄存器的指令集
mov eax,1 add eax,1
计算“1+1”的结果
iconst_1 iconst_1 iadd istore_0
优点就是可移植
缺点是执行速度相对来说会稍慢一些
方法调用详解
编译时就必须确定下来。这类方法的调用称为解析
编译期可知,运行期不可变
括静态方法
与类型直接关联
私有方法
外部不可被访问
适合在类加载阶段进行解析
静态分派
多见于方法的重载
静态分派发生在编译阶段, 因此确定静态分派的动作实际上不是由虚拟机来执行的。
动态分派
静态类型同样都是Human的两个变量man和woman在 调用sayHello()方法时执行了不同的行为
因为:这两个变量的实际类型不同
实现上,最常用的手段就是为类在方法区中建立一个虚方法表。 虚方法表中存放着各个方法的实际入口地址
类加载机制
类的生命周期-7个阶段
加载(Loading)、 验证(Verification)、准备(Preparation)、解析(Resolution)、 初始化(Initialization)、 使用(Using)和 卸载(Unloading)
其中验证、准备、解析3个部分统称为连接(Linking)
1.加载
1)通过一个类的全限定名来获取定义此类的二进制字节流。 2)将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构。 3)在内存中生成一个代表这个类的java.lang.Class对象,作为方法区这个类的各种数据的访问入口。
用户应用程序可以通过自定义类加载器参与
2.验证
文件格式验证、元数据验证、字节码验证、符号引用验证
3.准备
类变量分配内存并设置类变量初始值的阶段
进行内存分配的仅包括类变量(被static修饰的变量),而不包括实例变量
4.解析
将常量池内的符号引用替换为直接引用的过程
5.初始化
有且只有5种情况必须立即对类进行“初始化”
1)遇到new、getstatic、putstatic或invokestatic这4条字节码指令时
2)使用java.lang.reflect包的方法对类进行反射调用的时候
3)当初始化一个类的时候,如果发现其父类还没有进行过初始化
4)当虚拟机启动时,用户需要指定一个要执行的主类(包含main()方法的那个类)
5)当使用JDK 1.7的动态语言支持时,如果一个java.lang.invoke.MethodHandle实例最后的解析结果REF_getStatic、REF_putStatic、REF_invokeStatic的方法句柄,并且这个方法句柄所对应的类没有进行过初始化,则需要先触发其初始化
初始化阶段是执行类构造器<clinit>()方法的过程
初始化的单例模式(线程安全)
6.使用
7.卸载
类加载器
由加载它的类加载器和这个类本身一同确立一个类在Java虚拟机中的唯一性
加解密案例(deencrpt代码)
通过位的二进制异或运算进行加解密 (一次就是加密,再运算一次就是解密)
双亲委派模型
启动类加载器(Bootstrap ClassLoader)
使用C++语言实现,是虚拟机自身的一部分
扩展类加载器(Extension ClassLoader
应用程序类加载器
自定义类加载器
ClassLoader中的loadClass方法中的代码逻辑就是双亲委派模型
重写loadClass方法
另一种是重写findClass方法
Tomcat类加载机制
2.System 系统类加载器 加载tomcat启动的类
3 Common 通用类加载器 加载tomcat使用以及应用通用的一些类
4 webapp 应用类加载器
WebappClassLoader中loadClass方法
打破双亲委派!
但其父类仍然符合双亲委派,所以没问题!
6.JVM调优
调优的本质
并不是显著的提高系统性能
主要调的是稳定
减少GC调用次数和耗时
调优原则
1、大多数的java应用不需要GC调优 2、大部分需要GC调优的的,不是参数问题,是代码问题 3、在实际使用中,分析GC情况优化代码比优化GC参数要多得多; 4、GC调优是最后的手段
目的
GC的时间够小 GC的次数够少
Minor GC执行时间不到50ms; Minor GC执行不频繁,约10秒一次;
Full GC执行时间不到1s; Full GC执行频率不算频繁,不低于10分钟1次
GC调优
调优步骤
日志分析
1,监控GC的状态
2,分析结果,判断是否需要优化
3,调整GC类型和内存分配
4,不断的分析和调整
5,全面应用参数
阅读GC日志
主要关注MinorGC和FullGC 的 回收效率(回收前大小和回收比较)、 回收的时间
调优实战
项目启动GC优化
1、开启日志分析 -XX:+PrintGCDetails 发现有多次GC包括FullGC 2、调整Metadata空间 -XX:MetaspaceSize=64m 减少FullGC 3、减少Minor gc次数,增加参数 -Xms500m GC减少至4次 4、减少Minor gc次数,调整参数 -Xms1000m GC减少至2次 5、增加新生代比重,增加参数 -Xmn900m GC减少至1次 6、加大新生代,调整参数 -Xms2000m -Xmn1800m 还是避免不了GC,没有必要调整这么大,资源浪费
项目运行GC优化
使用jmeter同时访问三个接口,index、time、noblemetal 使用40个线程,循环2500次进行压力测试,观察并发的变化 jmeter的聚合报告的参数解释:
推荐策略
1.新生代大小选择
•响应时间优先的应用:尽可能设大
•吞吐量优先的应用:尽可能的设置大
•避免设置过小
2.老年代大小选择
响应时间优先的应用:老年代使用并发收集器
吞吐量优先的应用:一般吞吐量优先的应用 都有一个很大的新生代和一个较小的老年代
GC调优是个很复杂、很细致的过程, 要根据实际情况调整,不同的机器、 不同的应用、不同的性能要求调优的手段都是不同的
逃逸分析
JVM最激进的优化,最好不要调整相关的参数
-XX:+DoEscapeAnalysis:启用逃逸分析(默认打开) -XX:+EliminateAllocations:标量替换(默认打开) -XX:+UseTLAB 本地线程分配缓冲(默认打开)
如果是逃逸分析出来的对象可以在栈上分配的话, 那么该对象的生命周期就跟随线程了,就不需要垃圾回收
常用性能-测试指标
响应时间
并发数
吞吐量
性能优化手段
避免过早优化
进行系统性能测试
寻找系统瓶颈,分而治之,逐步优化
前端优化常用手段
浏览器/APP
减少请求数
合并文件
使用客户端缓存
启用压缩
浏览器(zip),压缩率80%以上
资源文件加载顺序
css放在页面最上面,js放在最下面。
减少Cookie传输
友好的提示
毕竟用户需要的是不要不理他
CDN加速
内容分发网络,本质是一个缓存
反向代理缓存
将静态资源文件缓存在反向代理服务器上, 一般是Nginx
Web组件分离
将js,css和图片文件放在不同的域名下。 可以提高浏览器在下载web组件的并发数
应用服务器性能优化
缓存
第一定律:优先考虑使用缓存优化性能
缓存是将数据存在访问速度较高的介质中
合理使用-准则
频繁修改的数据,尽量不要缓存, 读写比2:1以上才有缓存的价值。 缓存一定是热点数据。 应用需要容忍一定时间的数据不一致。 缓存可用性问题,一般通过热备或者集群来解决
分布式缓存与一致性哈希
集群
将用户的请求分配到多个机器处理,对总体性能有很大的提升
异步
同步和异步,阻塞和非阻塞
Servlet异步
多线程
消息队列
程序
代码级别
一个应用的性能归根结底取决于代码是如何编写的。
选择合适的数据结构
选择更优的算法
编写更少的代码
并发编程
资源的复用
单例模式 Spring中的bean
池化技术
存储性能优化
尽量使用SSD
定时清理数据或者按数据的性质分开存放
结果集处理-分页返回
子主题
子主题
8.新一代-ZGC
可以保证在TB级堆上的暂停时间非常短(TB级堆<10毫秒) ,对整个应用程序性能的影响非常小(<15%的吞吐量)
指针着色
指针表示虚拟内存中字节的位置。
ZGC仅适用于64位平台
ZGC指针使用42位来表示地址本身。 因此, 可以处理4TB的内存空间(2的42次方)
4位来存储指针状态
多重映射
将多个虚拟内存范围映射到物理内存
负载屏障
负载屏障是一个代码片段,它在线程从堆加载引用时运行
负载障碍检查引用的元数据位
标记
确定我们可以到达哪些对象的过程。 我们无法达到的被认为是垃圾
1.Stop The World阶段,寻找根引用并标记它们
2.并发-从根引用开始遍历对象图
3.Stop The World阶段,用来处理一些边缘情况,比如弱引用
ZGC使用marked0和marked1元数据位进行标记
重定位
重新映射
如何启用
-XX:+ UnlockExperimentalVMOptions -XX:+ UseZGC
目前ZGC是一个实验性GC,在生产平台上使用,还需要再考察
结论
ZGC打算以较低的应用程序暂停时间支持大堆。 为了实现这一目标,它使用了包括有色64位指针,负载屏障,重定位和重新映射在内的技术
7.编写高效优雅的java程序
面向对象
01、构造器参数太多
用JavaBeans模式, get和set 一行构造编程多行代码实现, 需要使用额外机制确保一致性和线程安全
用builder模式, 1、5个或者5个以上的成员变量 2、参数不多,但是在未来,参数会增加
02、不需要实例化的类应该构造器私有
03、不要创建不必要的对象
04、避免使用终结方法
如果有确实要释放的资源应该用try/finally。
05、使类和成员的可访问性最小化
迪米特法则
06、使可变性最小化
不提供任何可以修改对象状态的方法; 使所有的域都是final的。 使所有的域都是私有的。 使用写时复制机制。
07、复合优先于继承
08、接口优于抽象类
我不同意!
java是个单继承的(不能继承多个抽象类), 但是类允许实现多个接口
方法
09、可变参数要谨慎使用
10、返回零长度的数组或集合,不要返回null
Collections.EMPTY_LIST
11、优先使用标准的异常
IllegalArgumentException -- 调用者传递的参数不合适 IllegalStateException -- 接收的对象状态不对, NullPointerException UnsupportedOperationException –不支持的操作
通用程序设计
12、用枚举代替int常量
13、将局部变量的作用域最小化
14、精确计算,避免使用float和double
可以使用int或者long以及BigDecimal
15、当心字符串连接的性能
大量字符串拼接或者大型字符串拼接的时候, 尽量使用StringBuilder和StringBuffer
16、控制方法的大小
5.性能优化
内存溢出
栈溢出
方法死循环递归调用(StackOverflowError)、 不断建立线程(OutOfMemoryError)
堆溢出
不断创建对象,分配对象大于最大堆的大小(OutOfMemoryError)
直接内存
方法区溢出
在经常动态生产大量Class的应用中, CGLIb字节码增强,动态语言,大量JSP
内存泄漏
定义:程序在申请内存后,无法释放已申请的内存空间
长生命周期的对象持有短生命周期对象的引用
连接未关闭
数据库连接、网络连接和IO连接等
变量作用域不合理
1.一个变量的定义的作用范围大于其使用范围, 2.如果没有及时地把对象设置为null
内部类持有外部类
Hash值改变
对比
内存溢出:实实在在的内存空间不足导致; 内存泄漏:该释放的对象没有释放,常见于使用容器保存元素的情况下
避免
内存溢出:检查代码以及设置足够的空间 内存泄漏:一定是代码有问题
往往很多情况下,内存溢出往往是内存泄漏造成的
了解MAT
浅堆
指一个对象所消耗的内存
深堆
这个对象被GC回收后,可以真实释放的内存大小, 也就是只能通过对象被直接或间接访问到的所有对象的集合
incoming和outgoing
它引用了谁?谁引用了它?
命令工具
命令行工具
jps
列出当前机器上正在运行的虚拟机进程
-q :仅仅显示进程, -m:输出主函数传入的参数. -l: 输出应用程序主类完整package名称或jar完整名称. -v: 列出jvm参数, -Xms20m -Xmx50m是启动程序指定的jvm参数
jstat
监视虚拟机各种运行状态信息
显示:进程中的类装载、内存、垃圾收集、JIT编译等运行数据
jstat-gc 13616 250 10
-class (类加载器) -compiler (JIT) -gc (GC堆状态) -gccapacity (各区大小) -gccause (最近一次GC统计和原因)
jinfo
查看和修改虚拟机的参数
jinfo –sysprops 可以查看由System.getProperties()取得的参数 jinfo –flag 未被显式指定的参数的系统默认值 jinfo –flags(注意s)显示虚拟机的参数 jinfo –flag +[参数] 可以增加参数
jmap
用于生成堆转储快照(一般称为heapdump或dump文件)
jmap -dump:live,format=b,file=heap.bin <pid>
jhat(JVM Heap Analysis Tool)命令与jmap搭配使用, 来分析jmap生成的堆转储快照
jhat
jhat dump文件名 后屏幕显示“Server is ready.”的提示后, 用户在浏览器中键入 http://localhost:7000/就可以访问详情
使用jhat可以在服务器上生成堆转储文件分析
一般不推荐,毕竟占用服务器的资源
jstack
用于生成虚拟机当前时刻的线程快照
可视化工具
Jconsole
visualvm
GC重要参数
生产服务器推荐开启
-XX:-HeapDumpOnOutOfMemoryError 默认关闭,建议开启
-XX:HeapDumpPath=./java_pid<pid>.hprof 用来设置堆内存快照的存储文件路径
调优之前开启、调优之后关闭
-XX:+PrintGC 调试跟踪之 打印简单的GC信息参数:
-XX:+PrintGCDetails, +XX:+PrintGCTimeStamps 打印详细的GC信息
-Xlogger:logpath 设置gc的日志路,如: -Xlogger:log/gc.log
考虑使用
-XX:+PrintHeapAtGC, 打印堆信息
-XX:+TraceClassLoading 参数方法: -XX:+TraceClassLoading
-XX:+DisableExplicitGC 禁止在运行期显式地调用System.gc()
3.垃圾回收算法和收集器
why-learn
1、面试需要 2、GC对应用的性能是有影响的; 3、写代码有好处
判断对象存活
引用计数法
优点:快,方便,实现简单。 缺陷:对象相互引用时(A.instance=B同时B.instance=A), 很难判断对象是否该回收
子主题
可达性分析
“GC Roots”的对象作为起始点, 从这些节点开始向下搜索, 搜索所走过的路径称为引用链(Reference Chain)
当一个对象到GC Roots没有任何引用链相连时 则证明此对象是不可用的
GC-Roots-Set集合
1.当前虚拟机栈中局部变量表中的引用的对象 2.当前本地方法栈中局部变量表中的引用的对象 3.方法区中类静态属性引用的对象 4.方法区中的常量引用的对象
finalize
第二次标记
finalize可以完成对象的拯救, 但是JVM不保证一定能执行,所以请忘记这个“坑”
用在直接内存分配的回收
四种引用
强引用:一般的Object obj = new Object() ,就属于强引用
软引用 SoftReference
内存充足的时候不会回收它,而在内存不足时会回收它
非常适合于创建缓存
弱引用 WeakReference
无论内存充足与否,都会回收该对象的内存
实际运用(WeakHashMap、ThreadLocal)
虚引用 PhantomReference
幽灵引用,最弱,被垃圾回收的时候收到一个通知
两种GC
Garbage-Collection
打印GC详情 -XX:+HeapDumpOnOutOfMemoryError
Minor GC
特点: 发生在新生代上,发生的较频繁,执行速度较快 触发条件: Eden区空间不足\空间分配担保
Full GC
特点:主要发生在老年代上(新生代也会回收),较少发生,执行速度较慢
触发条件:
调用 System.gc() 老年代区域空间不足 空间分配担保失败
回收算法
复制算法(Copying)
新生代中的对象90%是“朝生夕死”的
-XX:SurvivorRatio Survivor区和Eden区的比值
标记-清除算法(Mark-Sweep)
1.首先标记所有需要回收的对象 2.统一回收被标记的对象
1.效率问题,标记和清除效率都不高 2.标记清除之后会产生大量不连续的内存碎片
标记-整理算法(Mark-Compact)
让所有存活的对象都向一端移动, 然后直接清理掉端边界以外的内存
分代收集
新生代使用复制算法 老年代使用标记-整理或者标记-清除算法
jps -v显示当前使用的垃圾回收器
垃圾回收器
并行:垃圾收集的多线程的同时进行。 并发:垃圾收集的多线程和应用的多线程同时进行。
Serial/Serial Old
最古老的,单线程,独占式,成熟,适合单CPU
-XX:+UseSerialGC 新生代和老年代都用串行收集器 -XX:+UseParNewGC 新生代使用ParNew,老年代使用Serial Old -XX:+UseParallelGC 新生代使用ParallerGC,老年代使用Serial Old
ParNew
Serial基本没区别,唯一的区别:多线程,多CPU的,停顿时间比Serial少
(ParallerGC)/Parallel Old
关注吞吐量的垃圾收集器, 高吞吐量则可以高效率地利用CPU时间
适合在后台运算而不需要太多交互的任务
吞吐量:CPU用于运行用户代码的时间与CPU总消耗时间的比值
Concurrent Mark Sweep (CMS)
获取最短回收停顿时间为目标的收集器
-XX:+UseConcMarkSweepGC , 一般新生代使用ParNew,老年代的用CMS
CMS收集器是基于“标记—清除”算法实现的
1.初始标记
STW-只是标记一下 GC Roots 能直接关联到的对象
2.并发标记
对象进行可达性分析,找到存活对象
3.重新标记
SYW-修正并发标记期间因用户程序继续运作 而导致标记产生变动的那一部分对象的标记记录
4.并发清除
优点:耗时最长的并发标记和并发清除过程收集器线程都可以与用户线程一起工作
缺点:
CPU资源敏感:因为并发阶段多线程占据CPU资源
浮动垃圾:由于CMS并发清理阶段用户线程还在运行着, 伴随程序运行自然就还会有新的垃圾不断产生
老年代空间使用率阈值(92%)
会产生空间碎片:标记 - 清除算法会导致产生不连续的空间碎片
G1垃圾回收器
-XX:+UseG1GC 使用G1垃圾回收器
划分成多个大小相等的独立区域(Region) 新生代和老年代不再物理隔离
算法:标记—整理 (old,humongous) 和复制回收算法(survivor)。题
1.Young GC -复制算法 选定所有新生代里的Region。通过控制新生代的region个数 来控制young GC的时间开销
2.Mixed GC 选定所有新生代里的Region, 外加根据global concurrent marking统计 得出收集收益高的若干老年代Region
Mixed GC不是full GC,它只能回收部分老年代的Region
如果mixed GC实在无法跟上程序分配内存的速度, 导致老年代填满无法继续进行Mixed GC, 就会使用serial old GC(full GC
G1是不提供full GC的
全局并发标记(global concurrent marking)
基本和CMS一样
特点
空间整合:不会产生内存碎片
可预测的停顿:
适合于大内存!
Stop The World现象 GC收集器和我们GC调优的目标就是尽可能的减少STW的时间和次数。
1.JVM内存结构
why理解JVM
写出更优雅的代码
排查问题-性能调优
面试必问
历史
HotSpot-sun-oracle
JRockit-bea
J9-IBM
Dalvik-google
未来的java-11
模块化 混合语言 多核并行 丰富语法 64位 更强的GC
JavaSE体系
JDK-JRE-JVM
标准版; JavaEE、JavaME
C++语言实现
运行时数据区
线程私有:程序计数器、虚拟机栈、本地方法栈 线程共享:堆、方法区
线程私有
程序计数器
唯一一个不会出现OutOfMemoryError情况的区域
多线程,需要恢复现场
Java方法,则指明当前线程执行的代字节码行数
Natvie方法,这个计数器值则为空(Undefined)
虚拟机栈
栈的大小缺省为1M,最小165K 可用参数 –Xss调整大小,例如-Xss256k
局部变量表
操作数栈
动态链接
返回地址
三部曲: 恢复上层方法的局部变量表和操作数栈、 把返回值(如果有的话)压入调用者栈帧的操作数栈中、 调整PC计数器的值以指向方法调用指令后面的一条指令
本地方法栈
native方法调用 JNI到了底层的C/C++
对应操作系统内存!
三者:生命周期和线程相同
线程共享
方法区-永久代
jdk1.7及以前:-XX:PermSize;-XX:MaxPermSize; jdk1.8以后:-XX:MetaspaceSize; -XX:MaxMetaspaceSize jdk1.8以后大小就只受本机总内存的限制
1.8:元空间
堆
-Xms:堆的最小值; -Xmx:堆的最大值; -Xmn:新生代的大小; -XX:NewSize;新生代最小值; -XX:MaxNewSize:新生代最大值;
运行时常量池
符号引用
在编译时用符号引用来代替引用类, 在加载时再通过虚拟机获取该引用类的实际地址
符号可以是任何形式的字面量, 只要使用时能无歧义地定位到目标即可
字面量
文本字符串:String a = "abc",这个abc就是字面量
八种基本类型
int a = 1; 这个1就是字面量
short\int\long\float\double\ char\byte\boolean\
声明为final的常量
常量池的变化
子主题
三者:涉及到生命周期管理和垃圾回收等概念
直接内存
使用Native函数库直接分配堆外内存(NIO)
通过-XX:MaxDirectMemorySize来设置
栈溢出
参数:-Xss256k
无限递归-java.lang.StackOverflowError
不断建立线程-OutOfMemoryError
一般演示不出,演示出来机器也死了