JVM:
堆:线程共享,存放对象实例,有垃圾回收机制,分为新生代(按照8:1:1分割为eden,survivor1和survivor2),和老年代
新生代存储新生的对象,新生代的对象经过15次复制算法后还存在的对象,会移到老年代中,老年代存储大对象和新生代剩下的对象。
年老代里存放的都是存活时间较久的,大小较大的对象,因此年老代使用标记整理算法。当年老代容量满的时候,会触发一次Major GC(full GC),回收年老代和年轻代中不再被使用的对象资源。
元空间:线程共享,运行的常量池
虚拟机栈:java执行方法的内存模型,先进后出,执行方法时候,
都会创建一个栈帧:操作数栈,局部变量表,返回值,动态链接
本地方法栈:调用本地的native的方法服务,会抛出 StackOverflowError 和 OutOfMemoryError 异常。
程序计数器:执行虚拟机字节码指令的地址。
解释器
代码生成器 代码优化器 目标代码生成器, 探测分析器
垃圾回收不会涉及到栈内存,涉及到堆
栈内存分配内存不是越大越好,越大是更多的方法调用而已
方法内局部变量是否安全?
1,共享的变量不是安全的,私有的是安全的
2,如果方法内局部变量没有逃离方法的作用访问,是线程安全的
java类加载分为5个阶段
加载,将class文件字节码内容加载到内存中,并将这些静态数据转换成方法区的运行数据结构,
然后生成一个代表这个类的java.lang.Class对象
链接:将java类的二进制文件合并到JVM的运行状态之中的过程
验证,确保加载的类信息符合JVM规范,没有安全方面问题
准备,正式为类变量(static)分配内存并设置类变量默认初始值的阶段,这些内存将在方法区中进行分配
解析,虚拟机常量池的富豪引用(常量名)替换为直接引用(地址)的过程
初始化:执行构造器<clinit>()方法的过程,类构造器<clinit>()方法是由编译期自动收集类中所有变量的赋值动作和静态代码块中的语句合并产生
(构造器是构造类信息的,不是构造该类对象的构造器)
当初始化一个类的时候。如果发现父类还没有进行初始化,则需要先触发其父类的初始化
虚拟机会保证一个类<clinit>()方法在多线程环境中被正确加锁和同步
使用
卸载
双亲委派机制:
当一个类加载器收到类加载请求时,首先不会自己去加载,委托自己的父类加载器去加载,
如果父类加载器还有父类,则进一步委托,
就通过递归的方式找到顶层的父类加载器加载。如果顶层父类加载器可以加载,就返回结果,
如果顶级父类加载器无法加载,子类加载器才会尝试加载。
类加载作用:
将class文件字节码内容加载到内存中,并将这些静态资源数据转换成方法区的运行时数据结构,
然后在堆中生成一个代表这个类的java.lang.Class对象,作为方法区中类数据的访问入口
类缓存:标准的javaSE类加载器可以按照要求查找类,一旦某个类被加载到类加载器中,
它将维持加载(缓存)一段时间,jvm垃圾回收机制可以回收这些class对象
类加载器:
启动类加载器
扩展类加载器
应用程序加载器
自定义加载器
-------------------------
cpu 占用过多:linux上
使用top定位那个进程对cpu占用过高
命令:ps H -eo pid,tid,%cpu|grep进程id (使用ps命令进一步定位哪个线程引起的cpu占用过高)
命令:jstack 进程id
可以根据线程id找到问题的线程,进一步定位到问题的代码的源码行号
程序运行时间长没有结果
命令:jstack 进程id
强引用:当内存不足时,jvm宁可出现oom错误,也不会回收。
软引用:当内存不足时,进行回收
弱引用:不管内存是否不足,进行对对象回收处理,就立即回收
虚引用:虚引用是最弱的一种引用关系,如果一个对象仅持有虚引用,那么它就和没有任何引用一样,它随时可能会被回收,
三色标记法
对象在标记过程中,根据标记情况,分成三类:
白色对象,表示自身未被标记;(白色对象会被当成垃圾对象)
灰色对象,表示自身被标记,但内部引用未被处理;
黑色对象,表示自身被标记,内部引用都被处理;(不会当成垃圾对象,不会被GC)
并发标记带来的三个问题:
1,多标
2,少标:标记程序在运行的过程中,用户线程依然会创建对象
新创建的对象默认都是黑色,躲过这次gc,下次有可能被释放掉
3,漏标:标记程序过程中引用链发生改变
条件一:灰色对象断开了白色对象的引用:即灰色对象原来的成员变量的引用发生了改变
条件二:黑色对象重新引用了该白色对象,即黑色对象成员变量增加了新的引用
解决的是:
1,增量更新:incremental update
解决条件二,通过写屏障记录下更新
2,原始快照 SATB
组合方法:
1,读屏障+重新标记
2,写屏障+增量更新
3,写屏障+原始快照
4,实际运用:
CMS:写屏障+增量更新
G1:写屏障+SATB
此时的解决办法就是有一个叫做写入屏障的东西。就是说,如果A已经被标记了(已经是黑色的了),
那么用户线程改动 A->C的时候,会把 C 变成灰色,这样,以后就可以搜索 C了。
具体的标记程
开始的时候,会任务所有的对象都是白色的;
利用根可达算法将根下的引用对象都标记为灰色
移动到 灰色对象中,将本对象 标记为黑色;
将灰色对象中所有引用对象标记为灰色;
重复第3第4步,直至扫完所有灰色对象;
完成后没有被标记为黑色的,或者全部是白色的,就为mark sweep出来的垃圾对象可以进行回收
如何判断一个对象是否可以被回收 GC roots,2个方法
1,引用计数法:给对象添加一个计数器,对象引用它,计数器就加1,引用失效就减一
任何时刻计数器值为零的对象,是不可能在被使用的,这个对于就是可回收对象。
2,可达性分析(根搜索方法):通过一系列为GC Roots的对象作为起始点,从这个被称为GC roots的对象开始向下搜索,
就是对象到达GcRoot的路径是否还有可达,即是否有可引用链,如果有,这表明对象还存在着引用,
如果没有,则表明该对象没有引用,在下一次垃圾回收时就会被回收。
哪些可以作为GC Roots的对象:
虚拟栈中引用的对象
方法区的类静态属性引用的对象
方法区中常量引用的对象
本地方法栈中native方法引用的对象
垃圾回收算法:
1,复制算法:
1、当Eden区满的时候,会触发第一次young gc,把还活着的对象拷贝到Survivor From区;当Eden区再次触发young gc的时候,会扫描Eden区和From区域,对两个区域进行垃圾回收,经过这次回收后还存活的对象,则直接复制到To区域,并将Eden和From区域清空。
2、当后续Eden又发生young gc的时候,会对Eden和To区域进行垃圾回收,存活的对象复制到From区域,并将Eden和To区域清空。
3、可见部分对象会在From和To区域中复制来复制去,如此交换15次(由JVM参数MaxTenuringThreshold决定,这个参数默认是15),最终如果还是存活,就存入到老年代
2,标记清除算法CMS
CMS全称 Concurrent Mark Sweep,是一款并发的、使用标记-清除算法的垃圾回收器
如果老年代使用CMS垃圾回收器,需要添加虚拟机参数-"XX:+UseConcMarkSweepGC"。
1,初始标记(InitialMarking) ,(初始化标记,整个过程STW); 只标记GC root直接关联的对象 标记为灰色。
标记GC Roots可达的老年代对象;
遍历新生代对象,标记可达的老年代对象
2,并发标记(Marking),该阶段GC线程和应用线程并发执行,遍历InitialMarking阶段标记出来的存活对象,
然后继续递归标记这些对象可达的对象。
3,预清理(Precleaning),与用户线程同时运行;
处理新生代已经发现的引用,
在并发标记阶段,如果老年代中有对象内部引用发生变化,会把所在的Card标记为Dirty
4,可被终止的预清理(AbortablePreclean) 与用户线程同时运行;
处理 From 和 To 区的对象,标记可达的老年代对象
和上一个阶段一样,扫描处理Dirty Card中的对象
5,并发重新标记,STW过程(FinalMarking)
遍历新生代对象,重新标记
根据GC Roots,重新标记
遍历老年代的Dirty Card,重新标记,这里的Dirty Card大部分已经在clean阶段处理过
6,并发清理, 这个阶段主要是清除那些没有标记的对象并且回收空间
7,并发重置,这个阶段并发执行,重新设置CMS算法内部的数据结构,准备下一个CMS生命周期的使用。
3,标记整理算法:G1 垃圾回收器可以同时回收新生代和老年代的对象,一个人负责所有。
内存结构: 把java堆内存拆分为多个大小相等的Regin,新生代可能包含了某些块,老年代可能包含某些块。
初始标记:进入"stop the world" ,就只标记Gc roots 直接引用的对象(线程栈内存中的局部变量,方法区中的类静态变量)。速度很快。
并发标记:允许系统程序的运行,同时进行"GC Roots"追踪,追踪所有存活对象(间接引用的对象)。
最终标记:进入"stop the world" ,根据并发标记记录的对象修改,最终标记存活对象,垃圾对象。
混合回收:计算老年代中每个Region的存活对象数量,存活对象占比,执行垃圾回收的预期性能和效率。然后停止系统运行,全力以赴进行垃圾回收,为了控制垃圾回收时间在指定的范围,会对部分的region进行回收。
主要的垃圾回收器
串行垃圾回收器(Serial):为单线程环境设计且只有一个线程进行垃圾回收,会暂停所有的用户线程
并行垃圾回收器(Parallel):多个垃圾线程进行工作,也会暂停用户线程
并发垃圾回收器(CMS):并发标记清除,通过写屏障和增量更新记录并发标记阶段新建立的引用关系,重新标记就是遍历这个记录
用户线程和垃圾回收器同时执行,不需要暂停用户线程
指针压缩 最大32g
吞吐量收集器(Parallel Scavenge)
G1垃圾回收器:并发标记整理,基于region 默认2m,用户线程和垃圾回收器同时执行,清理未标记到的对象
最大4g-64g
7个垃圾回收器详解:
新生代:serial(单线程)、ParNew(多线程)、Parallel Scavenge(吞吐量)
老年代:Serial Old单线程)、Parallel Old(多线程)、CMS(标记整理)
全堆:G1
Serial收集器
Serial Copying 新生代串行垃圾回收器
Parallel Scavenge新生代并行收集器
ParNew 新生代并行收集器(只在新生代区)
Serial Old 老年代串行 收集器
Parallel Old 老年代并行收集器
CMS 并发清除垃圾回收器
G1回收器 并发整理垃圾回收器
--------------------------------------------------------------------
jvm的参数值:
标配参数:
java -version
java -help
X参数
-Xint 解释执行
-Xcomp 第一次使用就编译成本地
-Xmixed 混合模式
XX参数(重点)
boolean类型: +开启,-关闭
是否打印gc收集细节: -XX:-PrintGCDetails
是否使用串行垃圾回收器:-XX:UseSerialGC
kv设置类型 -XX:属性key =值value
-XX:MetaspaceSize=128m
-XX:MaxTenuringThreshold=15
常用的参数介绍:
-Xms512m 设置JVM促使内存为512m。此值可以设置与-Xmx相同,以避免每次垃圾回收完成后JVM重新分配内存。
-Xmx512m ,设置JVM最大可用内存为512M。
-Xmn200m:设置年轻代大小为200M。
此处的大小是(eden+ 2 survivor space).与jmap -heap中显示的New gen是(eden+1 survivor space)不同的。
-Xss128k:设置每个线程的栈大小为128k。
参数及其默认值
描述
-XX:-DisableExplicitGC禁止调用System.gc();但jvm的gc仍然有效
-XX:+MaxFDLimit最大化文件描述符的数量限制
-XX:+ScavengeBeforeFullGC新生代GC优先于Full GC执行
-XX:+UseGCOverheadLimit在抛出OOM之前限制jvm耗费在GC上的时间比例
-XX:-UseConcMarkSweepGC对老生代采用并发标记交换算法进行GC
-XX:-UseParallelGC启用并行GC
-XX:-UseParallelOldGC对Full GC启用并行,当-XX:-UseParallelGC启用时该项自动启用
-XX:-UseSerialGC启用串行GC
-XX:+UseThreadPriorities启用本地线程优先级
性能调优参数列表:
参数及其默认值 描述
-XX:LargePageSizeInBytes=4m设置用于Java堆的大页面尺寸
-XX:MaxHeapFreeRatio=70GC后java堆中空闲量占的最大比例
-XX:MaxNewSize=size新生成对象能占用内存的最大值
-XX:MaxPermSize=64m老生代对象能占用内存的最大值
-XX:MinHeapFreeRatio=40GC后java堆中空闲量占的最小比例
-XX:NewRatio=2新生代内存容量与老生代内存容量的比例
-XX:NewSize=2.125m新生代对象生成时占用内存的默认值
-XX:ReservedCodeCacheSize=32m保留代码占用的内存容量
-XX:ThreadStackSize=512设置线程栈大小,若为0则使用系统默认值
-XX:+UseLargePages使用大页面内存
--------------------------------------------------
一个栈帧占 250*1024/1676=158.41
帧深度在变:
线上分配
轻量级锁
栈内存溢出
栈内存溢出StackOverflowError:
方法运行时栈的深度超过了虚拟机的最大深度导致。
栈帧过多导致:方法递归调用导致
栈帧过大导致:调用对象中又包含其对象,一直循环调用
堆内存溢出:oom
死循环导致,资源没有释放,垃圾回收器没有回收
jps-查看当前系统中有哪些java进程
jmap-查看堆内存占用情况 jmap -heap 进程id
元空间内存溢出:
场景:spring mybatis
会发生oom的区:
1,栈
2,堆
3,方法区
内存泄漏:程序在申请内存后,无法释放已申请的空间,
一次泄漏不会有多大影响但内存泄漏堆积后就是内存溢出。
物理空间没有满足需要: 就是你要的内存空间超过了系统实际分配给你的空间,
此时系统相当于没法满足你的需求,就会报内存溢出的错误。
内存溢出:程序申请内存,没有足够的内存供申请者使用,内存不够用,就是内存溢出。
内存不足,使用者太多
JVM性能调优的工具:
(1)jps(Java Process Status):输出JVM中运行的进程状态信息(现在一般使用jconsole)
(2)jstack:查看java进程内线程的堆栈信息。
(3)jmap:用于生成堆转存快照
(4)jhat:用于分析jmap生成的堆转存快照(一般不推荐使用,而是使用Ecplise Memory Analyzer)
(3)jstat是JVM统计监测工具。可以用来显示垃圾回收信息、类加载信息、新生代统计信息等。
(4)VisualVM:故障处理工具
5,寒泉子分析工具perfma
jsp工具:
查看当前系统中有哪些java进程
命令:jsp
jmap工具:
查看某个时刻堆内存占用情况
命令: jmap -heap 进程id
jconsloe工具
图形化界面,多功能检测工具,可以连续检测
命令:jconsloe