前言:以下内容参考自B站**谷视频及《深入理解Java虚拟机》一书,作者是先根据视频进行了系统化的学习,对jvm的相关内容有了一定的了解之后,把《深入理解Java虚拟机》这本书大概的看了一下,主要针对看视频过程中不太理解和讲的不清楚的地方,当看完书后,发现还是书上讲的自己看完后理解起来更加容易,当然不能排除视频留下基础的原因。
一、JVM概述
1、虚拟机:虚拟计算机,软件层面的;分为系统虚拟机和程序虚拟机,前者完全对物理计算机的仿真,后者专门为执行单个计算机程序而设计(典型是Java虚拟机,执行Java字节码(一个二进制文件)的虚拟计算机)
2、JVM的位置:JVM是运行在操作系统上,与硬件没有直接的交互
3、JVM的整体结构
4、Java代码执行流程:Java源码经过编译--》字节码文件--》JVM(类加载器、字节码校验器、执行引擎,字节码文件编译成机器指令)--》
5、JVM架构模型:基于栈的指令集架构(设计实现简单适用资源受限系统、可移植性好、使用零地址指令方式分配、跨平台性)和基于寄存器的指令架构(性能优秀、执行高效、以一地址指令,二地址指令和三地址指令为主、花费更少指令去完成一项操作),由于跨平台设计,Java的指令都是根据栈来设计
6、JVM的生命周期:
启动:通过引导类加载器(bootstrap class loader)创建一个初始类(initial class)来完成,这个类由虚拟机具体实现指定
执行:Java虚拟机的任务,执行Java程序,真正执行的是一个Java虚拟机的进程
退出:程序正常执行结束、出现异常或错误而异常终止、操作系统错误导致进程终止、某线程调用Runtime类或system类的exit方法,Runtime类的halt方法结束进程
二、类加载器子系统
1、类加载器与加载过程概述:类加载器子系统负责从文件系统或网络中加载Class文件,Class文件在开头有特定的文件标识,它只负责加载,至于是否运行,由ExecutionEngine决定,加载的类信息放在方法区中,方法区还存放运行时常量池信息
2、类的加载过程
加载(loading):通过类的全限定名获取定义此类的二进制字节流,将这个字节流所代表的静态存储结构转化为方法区的运行数据结构,在内存中生成一个代表这个类的java.lang.Class对象,作为方法区这个类的各种数据访问入口
链接(linking):
验证(verification):确保Class文件的字节流信息符合当前虚拟机要求(CA FE BA BE),保证被加载类的正确性,不会危害虚拟机自身安全;文件格式验证、元数据验证、字节码验证、符号引用验证
准备(preparation):为变量分配内存并设置该变量的默认初始值,即零值,这里不包含用final修饰的static,因为final在编译时就分配了,准备阶段会显式初始化,不会为实例变量分配初始化,类变量会分配到方法区,而实例变量随着对象一起分配到堆中
实例化对象你大体可以理解成为引用的意思...
比如说吧你爸爸有一台车...你想开...这个时候能,你得跟你老爸说,车借我开开,然后你老爸钥匙给你,你可以开了!要是你不去开口说,没钥匙肯定开不走!
实例化的意思也差不多,你把一个类作为一个对象,就当成是车,你想开...所以你跟编译器(也就是狭义的电脑)请求,ONECLASS TWOCLASS=NEW ONECLASS()就是一个跟机器借车开的语法,这里边ONECLASS和TWOCLASS就是个名字的差异,比如说你爸爸喜欢跟他自己的车叫亲爱的,那么你开着车的时候别管是谁的,你也可以叫它为宝贝...ONECLASS是不能改的,因为你得告诉机器你借的是哪台车...然后你借过来之后就随便你叫什么了....类下边的方法啊,公共属性都是可以借用过来的,好比说这台车有四个轮子一个方向盘就是属性,方法就是这台车可以正着开,也可以倒着开....所以无论你想正着开还是倒着开,你的前提都需要把车借过来才能开...实例化就是借车...调用方法就是借车之后的操作!
这就是实例化
解析(resolvation):将常量池中的符号引用转换为直接引用
初始化:任何一个类声明之后至少存在一个类构造器,执行类构造器方法<clinit>()的过程,不需要定义,是javac编译器自动收集类中所有类变量的赋值动作和静态代码块中的语句合并而来,构造器方法中指令按语句在源文件中出现的顺序执行,虚拟机必须保证一个类的<clinit>方法在多线程下被同步加锁,如下,只有一个线程抢占
3、类加载器分类:引导类加载器(BootStrap ClassLoader)和自定义类加载器(UserDefined ClassLoader),将所有派生于抽象类ClassLoader的类加载器都划分为自定义类加载器
获取系统类加载器
ClassLoader systemClassLoader = ClassLoader.getClassLoader();
启动类加载器(引导类加载器)使用C/C++实现,获取不到,嵌套在JVM内部,用来加载Java的核心类库,并不继承自java.lang.CLassLoader,没有父加载器,加载扩展类和应用程序类加载器,并指定为他们的父加载器
扩展类加载器,Java语言编写,派生于CLassLoader类,从java.ext.dirs系统属性指定目录加载
应用程序类加载器(系统类加载器AppClassLoader),是程序中默认的类加载器
用户自定义类加载器:应用场景(隔离加载类、修改类加载的方式、扩展加载源、防止源码泄漏)、继承ClassLoader,若没有复杂需求,继承URLClassLoader
4、双亲委派机制:JVM对Class文件按需加载,加载时采用双亲委派机制
优势:避免类的重复加载、保护程序的安全,防止核心API被篡改
沙箱安全机制:
5、补充
在JVM中表示两个Class对象是否为同一个类的必要条件:两个类的完整类名及包名必须一致;加载这个类的ClassLoader必须相同
若一个类是由用户类加载器加载,则JVM会将这个类加载器的一个引用作为类型信息的一部分保留在方区中,因为引导类加载器获取不到,所以不保存
破坏双亲委派模型:
打破双亲委派机制的有Tomcat、JDBC等
Tomcat容器中可能要部署多个应用,不同的应用之间可能依赖同一个第三方类库的不同版本,因此要保证每个应用程序的类库都是独立的、相互隔离的,同时
容器的类库不能和应用程序类库混淆,因此,如果使用默认的类加载器,由于只关注类的全限定类名,所以导致上述问题无法解决。因此Tomcat有多个自定义
类加载器,例如每个web应用程序都有一个对应的WebappClassLoader,只对当前应用可见,这里不同的WebappClassLoader类加载器就打破了双亲委派机制,不
再去让父类加载器去加载类,而是自己加载
三、运行时数据区概述及线程
1、JVM内存:内存是系统资源,是硬盘和CPU的中间仓库及桥梁;方法区jdk1.8改为元空间
方法区和堆是一个进程一份、程序计数器,本地方法栈和虚拟机栈是一个线程一份
2、线程:一个程序里的运行单元,JVM支持多线程;主要的后台系统线程有虚拟机线程、周期任务线程、GC线程、编译线程、信号调度线程
![](https://i-blog.csdnimg.cn/blog_migrate/d1221648be92d6bf812cd8cae6cd2172.png)
![](https://i-blog.csdnimg.cn/blog_migrate/93dd578846cd2d5836df789052bdc7b7.png)
![](https://i-blog.csdnimg.cn/blog_migrate/7ba9356a3755ee2cfbde32e89b77efb5.png)
五、虚拟机栈
1、虚拟机栈概述:栈是运行时的单位,而堆是存储的单位,即栈解决程序运行问题,堆解决数据存储问题;每个线程创建时都会创建一个虚拟机栈,其内部为一个个的栈帧,每个栈帧代表一个方法,虚拟机栈是线程私有的,生命周期和线程一致,主管Java程序的运行,保存方法的局部变量、部分结果,并参与方法的调用和返回;快速有效的分配存储方式,速度仅次于程序计数器;JVM对栈的操作只有两个,方法执行入栈,方法结束出栈;栈不存在垃圾回收问题,存在OOM
2、虚拟机栈常见异常与如何设置栈大小
设置栈大小,使用-Xss+参数,例如-Xss256m,在每个启动类设置
代码示例:
package com.example.test.jvm;
/**
* 模拟虚拟机栈的两种内存报错:栈溢出和内存溢出
* 设置虚拟机栈大小10m,-Xss10m,运行死循环,报错Exception in thread "main" java.lang.StackOverflowError
* 设置最大堆内存为10m,-Xmx10m,运行死循环,报错Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
*/
public class Stack {
public static void r(){
byte[] bytes = new byte[1024*1024];
r();
}
public static void main(String[] args) {
r();
}
}
3、栈的存储单位
栈中的数据都是以栈帧的格式存在,方法和栈帧一一对应;方法的结束方式,一是正常结束return,二是方法中出现未捕获的异常,以抛出异常的方式结束
4、栈帧的内部结构:局部变量表、操作数栈、动态链接、方法返回地址、一些附加信息
5、局部变量表:也称局部变量数组或本地变量表
定义为一个数字数组,主要用于存储方法参数和定义在方法体内的局部变量,这些数据类型包括各类基本数据类型、对象引用以及returnAddress类型;不存在数据安全问题;所需容量大小在编译器确定下来;变量只在当前方法调用中有效;方法结束局部变量表就销毁
6、局部变量表的最基本存储单元-Slot(变量槽)
32位以内的类型占用一个slot,64位的类型占两个slot;slot是可以重复利用的
静态变量与局部变量的对比:
按数据类型分:基本数据类型、引用数据类型;按在类中声明的位置分:成员变量(类变量-静态变量、实例变量)、局部变量;局部变量表中的变量是重要的垃圾回收根节点,只要被局部变量表中直接或间接引用的对象都不会被回收
6、操作数栈:在方法执行过程中,根据字节码指令,往栈中写入数据或取出数据;主要用于存储中间计算结果
i++和++i的区别?
7、栈顶缓存技术:将栈顶元素全部缓存在物理CPU的寄存器中,以此降低对内存的读写次数,提升执行引擎的执行效率
8、动态链接:每一个栈帧内部都包含一个指向运行时常量池中该栈帧所属方法的引用;Java源文件编译成字节码文件,所有变量方法引用都作为符号引用保存在常量池里,动态链接就是将这些符号引用转换为调用方法的直接引用
9、方法的调用:
静态绑定与动态绑定
绑定是一个字段、方法或者类在符号引用被替换为直接引用的过程
虚方法和非虚方法:
非虚方法:方法在编译期就确定了具体的调用版本,这个版本在运行时不可变;静态方法、私有方法、final方法、实例构造器、父类方法
10、线程安全:如果只有一个线程可以操作此数据,则必是线程安全;若有多个线程操作,需要考虑同步机制来实现线程安全
六、本地方法接口和本地方法栈
1、本地方法:一个Native Method就是一个Java调用非Java代码的接口
2、本地方法栈:Java虚拟机栈管理Java方法调用,本地方法栈管理本地方法的调用;线程私有;允许实现为固定或者可动态扩展的内存大小
七、堆
1、堆的核心概述:
一个JVM实例只有一个堆内存,是Java内存管理的核心;在JVM启动时创建;大小可以调整;堆处于物理上不连续,但在逻辑上是连续的;
内存细分:现代垃圾收集器大部分基于分代收集理论设计
2、设置堆空间大小与OOM
设置的其实是年轻代+老年代的大小,并不包括元空间的大小;默认初始内存是物理电脑内存大小/64;默认最大内存是物理电脑内存/4
开发中建议将初始堆内存和最大堆内存设置成相同的值
查看设置的参数:jps / jstat -gc 进程id;或者idea参数-XX:+PrintGCDetails
OOM:所需内存空间超出设置的最大堆空间
3、年轻代和老年代
新生代可以划分为Eden空间、Survivor0空间和Survivor2空间(也叫做from区、to区)
默认新生代占堆的1/3,其中Eden:Survivor:Survivor=8:1:1,-XX:-UseAdaptiveSizePolicy关闭自适应内存分配策略,+号是打开;老年代占堆的2/3;-NewRatio设置新生代与老年代比例,默认1:2
4、图解对象分配过程
Eden区满了之后,执行Minor GC,没有被回收的放入Survivor区,设置其age为1;放入的Survivor区一直是放到空的那个区;当age达到15时,对象放入老年区
针对幸存者s0和s1区,复制之后有交换,谁空谁是to;关于垃圾回收,频繁在新生代收集,很少在老年代收集,几乎不在元空间收集
5、Minor GC、Major GC和Full GC
JVM进行GC时,大部分时候回收的都是指新生代
触发Full GC的情况:
6、内存分配策略
针对不同年龄段的对象分配原则如下:
优先分配到Eden
大对象直接分配到老年代(尽量避免过多大对象)
长期存活的对象分配到老年代
动态年龄判断(若Survivor区中相同年龄的所有对象大小的总和大于Survivor空间的一半,则将年龄大于或等于该年龄的对象直接放入老年代)
空间分配担保(-XX:HandlePromotionFailure)
7、对象分配过程:TLAB
TLAB空间很小,之占Eden的1%,每个线程私有
8、堆空间的参数设置
-XX:+PrintFlagsInitial 查看所有参数的默认初始值
-XX:+PrintFlagsFinal 查看所有参数的最终值
-Xms 初始堆空间内存,默认物理内存1/64
-Xmx 最大堆内存 默认物理内存的1/4
设置 -Xms和-Xmx的值相同:
为了防止垃圾收集器在最小、最大之间收缩堆而产生额外的时间消耗,通常设置为相等值
-Xmn 设置新生代大小(初始值及最大值)
-XX:NewRatio 配置新生代与老年代在堆中的占比
-XX:SurvivorRatio 设置新生代中Eden和S0/S1的占比
-XX:MaxTenuringThreshold 设置新生代垃圾的最大年龄
-XX:+PrintGCDetails 输出详细的GC日志
打印简要GC信息:-XX:+PrintGC 、-verbose:gc
-XX:handlePromotionFailure 是否设置空间分配担保
9、堆是分配对象存储的唯一选择吗?虽然有逃逸分析,但是不成熟,hostpot不支持,所以是唯一的
如果经过逃逸分析,一个对象没有逃逸出方法的话,那么可能被优化成栈上分配
判断是否发生了逃逸分析,看new的对象是不是在方法外被调用,结论:能用局部变量的,就不要在方法外定义
10、代码优化
八、方法区
1、栈、堆、方法区的交互关系
2、方法区的理解
方法区看作是一块独立于Java堆的内存空间;和堆一样各个线程共享;大小决定了系统可以保存多少各类,若定义太多类,会导致OOM(如加载大量的第三方jar包;tomcat部署的工程过多;大量动态的生成反射类);jvm关闭方法区释放;jdk7之前,习惯上把方法区称为永久代;jdk8开始,使用元空间取代永久代
3、方法区大小的设置与OOM
元空间大小设置:-XX:MetaspaceSize 和 -XX:MaxMetaspaceSize指定;默认依赖于平台,windows下,默认21M,没有最大限制,值是-1;为了避免出现频繁GC,建议初始值设大一些
方法区存储类信息,若要OOM,就要创建多一些类
4、方法区的内部结构
主要存储类型信息、常量、静态变量、即时编译器编译后的代码缓存
全局常量 static final在编译期间加载
5、运行时常量池:具有动态性;通过索引访问
存储的数据类型有数量值、字符串值、类引用、字段引用、方法引用
6、方法区的演进细节
为什么替换
静态引用对应的对象实体始终都在堆里;只要是对象实例必然会在Java堆中分配
7、方法区的垃圾收集:可以回收也可以不回收
方法区的垃圾收集主要回收两部分内容:常量池中废弃的常量和不再使用的类型
方法区内常量池中主要存放两大类:字面量和符号引用
8、总结
九、对象相关
1、对象的实例化
new方式的变形:1、XXX的静态方法;2、XXxBuilder/XXxFactory的静态方法
Class的newInstance():反射的方式,只能调用空参的构造器,权限必须是public
Constructor的newInsstance:可以调用空参、带参的构造器,权限没要求
clone:不调用任何构造器,当前类需要实现Clonable接口,实现clone
使用反序列化:可以从文件、网络中获取一个对象的二进制流
判断对象对应的类是否加载、链接、初始化
为对象分配内存:先计算对象占用空间大小;
处理并发安全问题
初始化分配到的空间
设置对象的对象头
执行init方法进行初始化:显示初始化、代码块中初始化、构造器中初始化
2、对象的内存布局
pubic class account{
private String id;
private String name;
Acct acct;
}
class Acct{
}
3、对象访问定位
主要两种:句柄访问和直接指针;hotspot使用直接指针方式
十、执行引擎
1、概述
JVM的核心组成之一,主要将字节码指令解释/编译为对应平台上的本地机器指令
2、java代码编译和执行的过程
3、热点代码探测--判断是否需要JIT编译器进行即时编译
十一、String Table
1、String的基本特性
String是一个类,在jdk8及以前内部定义了final char[] value用于存储字符串数据,jdk9时改为byte[];不可变性,定义相同的对象,只是将对象指向值,更改对象,会新增一个值,这个值放在字符串常量池中;字符串常量池中不会存储相同内容的字符串;
2、String的内存分配
StringTable为什么要调整? permSize默认比较小、永久代垃圾回收频率低;jdk8后字符串常量池放入堆中
3、字符串拼接操作
intern方法:判读字符串常量池中是否存在xxx的值,若存在,返回xxx的地址;若不存在,则在常量池中加载xxx并返回其地址
字符串拼接操作不一定使用StringBuilder,若拼接符号左右两边都是字符串常量或常量引用,则仍然使用编译器优化,即非StringBuilder的方式
针对final修饰类、方法、基本数据类型、引用数据类型的量的结构时,能使用上final的时候尽量使用
通过StringBuilder的append方式添加字符串的效率远高于String的字符串拼接方式
StringBuilder的append自始至终只创建过一个StringBuilder对象;String方式创建多个StringBuilder和String对象
内存中由于String方式创建多个StringBuilder和String对象,内存占用更多,进行GC更慢
实际开发中,如果确定字符串长度不高于某个限定值highLevel,建议使用构造器
4、intern()的使用
如何保证变量s指向的时字符串常量池中的数据呢?
new String("ab")会创建几个对象?
这两个对象的值都为ab,但是它们的引用地址不同
new String("a")+new String("b")会创建几个对象?
面试题
十二、垃圾回收
1、垃圾回收概述
什么是垃圾?在运行程序中没有任何指针指向的对象
Java自动内存管理,无需开发人员介入,降低内存泄漏和内存溢出的风险
Java堆是垃圾收集器的工作重点
2、垃圾回收的相关算法
a、标记阶段:判断对象存活,需要区分内存中哪些对象存活,哪些死亡(不再被任何存活的对象继续引用)
①引用计数算法(由于缺陷三,Java不使用)
②可达性分析算法(根搜索算法、追踪性垃圾收集)
GC ROOT包括以下元素:
除了上述内容,分代收集和局部回收也可以临时性的加入GC ROOTs集合;
可达性分析需要在能确保一致性的快照下进行,这时整个系统就像停在某个时间节点,不会出现在分析过程中对象的引用关系还在发生变化,这就导致GC进行时
必须停顿所有Java执行线程(STW),而这个停顿的点也被称为安全点(safePoint),而这时应该考虑怎样控制线程到达安全点就停下来,一种方案是抢先式中
断(在发生GC时中断所有线程,检查是否到达安全点,没有就恢复线程,基本不使用),另一种是主动式中断(设置一个标志,各个线程轮询检查,发现中断标
志就将自己挂起)
3、对象的finalization机制
虚拟机中的对象有三种状态?
对象是否可以回收?
b、清除阶段:当成功区分出存活/死亡对象后,GC接下来就要执行垃圾回收
①标记-清除算法(Mark-Sweep)
所谓清除不是真的置空,而是把需要清除的对象地址保存在空闲的地址列表中,有新对象加载时,判断垃圾的位置空间是否够用,若够用直接存放
缺点:效率不算高、在GC时要停掉整个应用程序、清理出的空闲内存不连续,产生内存碎片
②复制算法(Copying)--主要用于survivor区
优点:运行高效、内存空间连续
缺点:需要两倍的内存空间、若垃圾对象少,复制算法反而效率变低(要复制的对象多)
③标记-压缩算法(标记-整理、Mark-Compact)
小结
分代收集算法:对于不同生命周期的对象采用不同的垃圾收集方式
增量收集算法
分区算法
3、垃圾回收的相关概念
a、System.gc()的理解
触发Full GC,同时对老年代和新生代进行回收;无法保证对垃圾收集器的调用;
调用runFinalizztion方法会强制调用finalize方法,确保gc执行
b、内存溢出OOM和内存泄漏Memory Leak
OOM:没有空闲内存,并且垃圾收集器也无法提供更多内存
没有空闲内存的原因?
若超大对象已经超出了堆内存的最大值,就不会执行GC;否则OOM之前一定会执行GC,GC后内存不够才报OOM
Memory Leak:只有对象不会再被程序使用且GC又不能回收
错误例子:循环引用,Java不使用引用计数算法
c、Stop The World的理解
所有GC事件中,产生应用的停顿,停顿产生时整个应用程序线程暂停,没有响应,简称STW
d、垃圾回收的并行与并发
程序中:
并发:在操作系统中,在一个时间段中有几个程序都处于 已启动运行到运行完毕之间,且这几个程序都是在同一个处理器上运行;并发不是真正意义上的同时进行
并行:当系统中有一个以上的CPU时,当一个CPU执行一个进程时,另一个CPU可以执行另一个进程,两个进程互不抢占CPU资源,可以同时进行
垃圾回收中:
并行:用户线程和垃圾收集线程同时执行(但不一定是并行的,可能会交替进行)
e、引用
强引用:默认的引用类型,强引用的对象是可触及的,是造成内存泄漏的主要原因,引用关系存在永不回收,宁愿报OOM
软引用:内存不足即回收,内存足够不回收,不会导致OOM,主要描述一些非必需的对象,主要做缓存
弱引用:发现即回收(执行GC即回收),应用场景做缓存
虚引用:用于对象回收跟踪
十三、垃圾回收器
1、垃圾回收器分类
按线程数分,可以分为串行垃圾回收器和并行垃圾回收器
按工作模式分,可以分为并发式垃圾回收器和独占式垃圾回收器
按碎片处理方式分,可以分为压缩式垃圾回收器和非压缩式垃圾回收器
按工作的内存空间分,可以分为年轻代垃圾回收器和老年代垃圾回收器
2、评估GC的性能指标
主要看吞吐量和暂停时间
现在的标准:在最大吞吐量优先的情况下,降低暂停时间
3、不同垃圾回收器概述
7款经典垃圾回收器
它们与垃圾分代之间的关系
它们之间的组合关系
查看默认的垃圾回收器
Serial回收器:串行回收
采用复制算法、串行回收和STW机制的方式执行内存回收,主要回收年轻代
Serial Old采用标记-压缩算法,回收老年代
使用-XX:+UseSerialGC使用serial垃圾回收器
ParNew回收器:并行回收,用于新生代
同样采用复制算法、STW机制
-XX:UseParNewGC开启,-XX:ParallelGCThreads修改线程数量
Parallel Scavenge回收器:吞吐量优先
采用复制算法、并行回收和STW机制,目标是达到一个可控制的吞吐量,适合后台运算而不需要太多交互的任务;
Parallel Old采用标记-压缩算法,并行回收和STW机制,回收老年代
JDK8默认垃圾回收器,相关参数设置
CMS垃圾回收器:低延迟,Concurrent-Mark-Sweep
第一次实现了让垃圾收集线程和用户线程同时工作,采用标记-清除算法
初始标记阶段:出现STW,仅仅是标记出GC Roots能直接关联到的对象,速度非常快
并发标记阶段:从GC Roots的直接关联对象开始遍历整个对象图的过程,耗时长不停顿用户线程
重新标记阶段:修正并发标记期间,因用户程序继续运行而导致标记产生变动的那一部分对象的标记记录,STW
并发清除阶段:清理删除掉标记阶段的已经死亡的对象,释放内存空间
优点:并发收集、低延迟
缺点:会产生内存碎片、对CPU资源敏感、无法处理浮动垃圾
G1回收器:区域化分代式
官方给G1设定的目标是在延迟可控的情况下获得尽可能高的吞吐量
优势:
①并行与并发:并行-》回收期间,可以多个GC线程同时工作,此时用户线程STW;并发-》可以与应用程序交替执行,不会阻塞所有用户线程
②分代收集:区分年轻代和老年代;将堆分为若干个空间,称为Region
③空间整合:内存的回收以region作为基本单位,region之间是复制算法,但整体上实际可以看作标记-压缩算法
④可预测的停顿时间模型:每次根据允许的收集时间,优先回收价值最大的Region,保证有限时间内获取尽可能高的收集效率
参数设置:JDK9默认使用G1
使用场景
Region:在JVM中大小相同,生命周期相同,逻辑上连续
具体回收:
当Eden空间耗尽,G1启动一次年轻代垃圾回收;
并发标记过程
混合回收:只是回收一部分老年代,而不是全部的,可以选择回收哪部分老年代;老年代默认8次回收,先回收内存分段中垃圾多的内存分段
优化建议
总结
GC日志分析
十四、性能调优
1、概述
性能优化的步骤
4个测试指标及相关关系,主要关注a和b调优
a、停顿时间(或响应时间)
b、吞吐量:对单位时间内完成的工作量,吞吐量为1-1/(1+n) -XX:GCTimeRatio=n
c、并发数:同一时刻,对服务器有实际交互的请求数
d、内存占用:Java堆区所占的内存大小
相互间的关系:以高速路为例,吞吐量是每天通过的车辆数据;并发数是高速路上正在行驶的车辆数目;相应时间是车速
2、JVM命令行监控工具相关
①jps:Java Process Status,查看正在运行的Java进程,语法jps 【-options】 【-hostid】
options参数
②jstat:JVM Statistics Monitoring Tool,查看JVM统计信息(类装载、内存、垃圾收集)
interval:用于指定输出统计数据的周期,单位为毫秒,即查询间隔
count:用于指定查询的总次数
-t:可以在输出信息前加上一个Timestamp列,显示程序的运行时间,单位是秒
-h:可以在周期性数据输出时,输出多少行数据后输出一个表头信息
③jinfo:实时查看和修改JVM配置参数
④jmap:导出内存映像文件和内存使用情况
使用1:导出内存映像文件:
若要监控OOM推荐自动方式,出现OOM就输出dump文件
jmap在安全点执行的,快照的分析结果可能存在偏差
⑤jhat:jdk自带堆分析工具,分析dump文件
访问localhost:7000查看,jdk9后删除了jhat,推荐使用visualVM
⑥jstack:生成虚拟机指定进程当前时刻的线程快照(虚拟机堆栈跟踪)
基本使用:jstack pid;jstack pid >d:/1.txt 写入文件,用于定位线程长时间等待
⑦jcmd:多功能命令行
⑧jstatd:远程主机信息收集
3、JVM监控及诊断的GUI工具
①Jconsole:jdk/bin目录下,启动jconsole.exe即可
②visualVM:jdk/bin目录下
生成dump文件,右键进程或者监视模块;生成线程dump也如此
抽样器可以查看占用cpu时间长的方法等
4、内存泄漏
对象不会再被程序使用但又无法被jvm回收
内存泄漏的8种情况
①静态集合类:
②单例模式:
③内部类持有外部类:
④各种连接、如数据库连接、网络连接和IO连接未关闭
⑤变量不合理的作用域
⑥改变哈希值
⑦缓存泄漏
⑧监听器和回调
5、JVM参数选项类型
①标准参数选项:以-开头,使用较少,运行java可以查看
②-X参数选项:运行java -X命令可以查看所有选项
③-XX参数选项:
添加JVM参数
设置堆内存:-Xms3550m最小堆内存、-Xmx3550m最大堆内存
6、OOM参数设置
7、GC日志分析:
控制台打印日志:-XX:+PrintGCDetail(详细日志)、-XX:+PrintGC
[GC (Allocation Failure) [PSYoungGen: 2040K->504K(2048K)] 5225K->5257K(7680K), 0.0101039 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[Full GC (Ergonomics) [PSYoungGen: 504K->0K(2048K)] [ParOldGen: 4753K->4929K(5632K)] 5257K->4929K(7680K), [Metaspace: 3453K->3453K(1056768K)], 0.0841143 secs] [Times: user=0.23 sys=0.00, real=0.09 secs]
GC和Full GC用来区分这次垃圾收集的停顿类型,而不是区分新生代和老年代,如果是Full GC说明这次垃圾回收发生STW
PSYoungGen代表垃圾收集器以及年轻代
方括号内2040K->504K(2048K)代表 GC前该内存已用容量->GC后该内存已用容量(该内存的总容量),一一对应的关系
方括号外5225K->5257K(7680K)代表 GC前Java堆已用容量->GC后Java堆已用容量(Java堆总容量),一一对应
0.0101039 secs代表GC所用时间,单位是秒
[Times: user=0.00 sys=0.00, real=0.00 secs]与Linux的time命令输出时间一致