1.JAVA内存区域
**线程私有:**虚拟机栈,本地方法栈(native方法 ),程序计数器
**线程公有:**堆,1.7之前方法区常量池,1.8之后元空间
2.JAVA堆
- minor gc 年轻代gc
- full gc 整个堆的gc ,暂停应用程序,并发问题,各种空指针,清理不干净
1).进入老年代的几种方式
1.长期存活的对象,当对象gc15次之后,可以修改但是最大15.取决于对象头种只用4bit表示年代信息
2.动态年龄判断,当该块的servivor区大于50%,会从低年龄累加到超过servivoer区的一半之后的对象,会一次性放到老年代
3.大对象,直接放入
4.to servivor区被占满后
5.老年代担保机制?
6.当minor gc时 servivor区放不下的对象会直接放入老年代
*对象 Eden -> form servivor -> to servivor => old
直接内存并不是虚拟机运行时数据区的一部分,也不是虚拟机规范中定义的内存区域,但是这部分内存也被频繁地使用。而且也可能导致 OutOfMemoryError 错误出现。
为什么要将永久代 (PermGen) 替换为元空间 (MetaSpace) 呢?
- 整个永久代有一个 JVM 本身设置固定大小上限,无法进行调整,而元空间使用的是直接内存,受本机可用内存的限制,虽然元空间仍旧可能溢出,但是比原来出现的几率会更小。
直接内存
- 直接内存并不是虚拟机运行时数据区的一部分,也不是虚拟机规范中定义的内存区域,但是这部分内存也被频繁地使用。而且也可能导致 OutOfMemoryError 错误出现。
2).对象的创建
类的加载检查,分配内存空间 ,设置对象头,执行init方法
1.类加载检查:虚拟机遇到一条 new 指令时,首先将去检查这个指令的参数是否能在常量池中定位到这个类的符号引用,并且检查这个符号引用代表的类是否已被加载过、解析和初始化过。如果没有,那必须先执行相应的类加载过程。
2.分配空间
3.类的初始化
4.设置对象头
5.执行init方法
3.类的加载过程
加载,验证,准备,解析,初始化,使用,卸载
加载,验证,准备,解析,初始化,使用,卸载
加载:在硬盘上查找并通过IO读入字节码文件,使用到类时才会加载,例如调用 类的main()方法,new对象等等
验证:校验字节码文件的正确性
准备:给类的静态变量分配内存,并赋予默认值
解析:将符号引用替换为直接引用,该阶段会把一些静态方法(符号引用,比如 main()方法)替换为指向数据所存内存的指针或句柄等(直接引用),这是所谓的静态链 接过程(类加载期间完成),动态链接是在程序运行期间完成的将符号引用替换为直接 引用,下节课会讲到动态链接
初始化:对类的静态变量初始化为指定的值,执行静态代码块
4.对象的内存布局
1.对象头(锁信息,gc年代,hashcode,类的元数据,锁的标志位)
2.对象的实例数据
3.对齐占位(如果时数组对象,那么会是数组信息)
5).如何判断一个对象是否死亡
1.引用计数器法:当对象存在引用,则引用计数+1,不存在引用,引用计数为0.不能避免循环引用
2.可达性分析:gc root根
可作为 GC Roots 的对象包括下面几种:
1.虚拟机栈(栈帧中的本地变量表)中引用的对象
2.本地方法栈(Native 方法)中引用的对象
3.方法区中类静态属性引用的对象
4.方法区中常量引用的对象
5.所有被同步锁持有的对象
6.jni的引用对象
6).String 和常量池
只要使用 new 方法,便需要创建新的对象。
7).如何判断一个类是无用的类
** 方法区主要回收的是无用的类**
- 该类所有的实例都已经被回收,也就是 Java 堆中不存在该类的任何实例。
- 加载该类的 ClassLoader 已经被回收。
- 该类对应的 java.lang.Class 对象没有在任何地方被引用,无法在任何地方通过反射访问该类的方法。
8).HotSpot 为什么要分为新生代和老年代?
比如在新生代中,每次收集都会有大量对象死去,所以可以选择”标记-复制“算法,只需要付出少量对象的复制成本就可以完成每次垃圾收集。而老年代的对象存活几率是比较高的,而且没有额外的空间对它进行分配担保,所以我们必须选择“标记-清除”或“标记-整理”算法进行垃圾收集。
3.双亲委派模式
上面的类加载过程主要是通过类加载器来实现的,Java里有如下几种类加载器 **启动类加载器:**负责加载支撑JVM运行的位于JRE的lib目录下的核心类库,比如 rt.jar、charsets.jar等 扩展类加载器:负责加载支撑JVM运行的位于JRE的lib目录下的ext扩展目录中 的JAR类包 **应用程序类加载器:**负责加载ClassPath路径下的类包,主要就是加载你自己写 的那些类 **自定义加载器:**负责加载用户自定义路径下的类包
加载某个类时会先委托父加载器寻找目标类,找不 到再委托上层父加载器加载,如果所有父加载器在自己的加载类路径下都找不到目标类,
则 在自己的类加载路径中查找并载入目标类。比如我们的Math类,最先会找应用程序类加载器加载,应用程序类加载器会先委托扩展类加载器加载,扩展类加载器再委托启动类加载器,顶层启动类加载器在自己的类加载路径 找了半天没找到Math类,则向下退回加载Math类的请求,扩展类加载器收到回复就自己 载,在自己的类加载路径里找了半天也没找到Math类,又向下退回Math类的加载请求给 用程序类加载器,应用程序类加载器于是在自己的类加载路径里找Math类,结果找到了 自己加载了。
双亲委派机制说简单点就是,先找父亲加载,不行再由儿子自己加载
为什么要设计双亲委派机制? **沙箱安全机制:**自己写的java.lang.String.class类不会被加载,这样便可以防止 核心API库被随意篡改 **避免类的重复加载:**当父亲已经加载了该类时,就没有必要子ClassLoader再加 载一次,保证被加载类的唯一性
Tomcat打破双亲委派模式:webappClassLoader加载自己的目录下的class文件,不会传递给父类加载器,打破了双亲委派机制。
4.Gc Roots
可作为 GC Roots 的对象包括下面几种:
- 虚拟机栈(栈帧中的本地变量表)中引用的对象
- 本地方法栈(Native 方法)中引用的对象
- 方法区中类静态属性引用的对象
- 方法区中常量引用的对象
- 所有被同步锁持有的对象
5.三色标记
要找出存活对象,根据可达性分析,从GC Roots开始进行遍历访问,可达的则为存活对象:
白色:*尚未被GC访问过的对象,如果全部标记已完成依旧为白色的,称为不可达对象,既垃圾对象。
灰色:*本对象已访问过,但是本对象的子引用对象还没有被访问过,全部访问完会变成黑色,属于中间态(本对象的孩子节点还没有访问)。
黑色:*本对象已经被GC访问过,且本对象的子引用对象也已经被访问过了(本对象的孩子节点也都被访问过)。
标记过程:
-
初始时,所有对象都在 【白色集合】中;
-
将GC Roots 直接引用到的对象 挪到 【灰色集合】中;
-
从灰色集合中获取对象:
3.1. 将本对象 引用到的 其他对象 全部挪到 【灰色集合】中.
3.2. 将本对象 挪到 【黑色集合】里面。
重复步骤3,直至【灰色集合】为空时结束。
结束后,仍在【白色集合】的对象即为GC Roots 不可达,可以进行回收。
6.垃圾回收算法
1.标记-清除 (1.效率问题,缺点产生不连续的内存空间)
2.标记-复制 (常用于eden区)(将存活的对象复制到空闲区,然后变为运行区,将旧运行区的内存清空即可变为空闲区,既保证了内存连贯性,效率也得到保证,无效对象较多的情况下,效率较高,清理后无内存碎片,但是占用内存,某一时刻只能使用其中一块内存区,内存的使用率较低)
3.标记-整理 (常用于老年代)(对已标记可被清除的对象进行垃圾回收,然后对存活的对象进行内存整理 多了一步,对象需要移动,影响整体GC的效率)
7.分代收集算法
新生代用标记-复制算法,老年代用标记-整理、标记-清除算法
Minor GC(young GC):新生代的垃圾回收,暂停时间短(STW stop the world 暂停所有引用程序线程,等待垃圾回收完成)
Mixed GC:新生代 + 老年代 部分 区域的垃圾回收, G1 收集器特有的
Full GC:新生代 + 老年代 完整 垃圾回收,暂停时间(STW)长,应该尽量避免
8.JVM 垃圾回收器
串行垃圾收集器:
使用单线程进行垃圾回收,堆内存较小,适合个人电脑
Serial:作用于新生代,采用复制算法
Serial Old:作用于老年代,采用标记-整理算法
垃圾回收时,只有一个线程在工作,并且java应用中的所有线程都要暂停(STW),等待垃圾回收的完成
并行垃圾收集器:
是一个并行垃圾回收器,jdk8 默认使用此垃圾回收器
Parallel New:作用于新生代,采用复制算法
Parallel Old:作用于老年代,采用标记-整理算法
垃圾回收时,多个线程在工作,并且java应用中的所有线程都要暂停(STW),等待垃圾回收的完成
1.Serial收集器 (串行收集器,单线程收集器 新生代采用标记-复制算法,老年代采用标记-整理算法。)
2.ParNew收集器 (Serial收集器的多线程版本 新生代采用标记-复制算法,老年代采用标记-整理算法。)
3.Parallel Scavenge收集器(高效利用cpu的收集器 标记-复制)
4.Serial Old收集器(老年代版本的收集器)、
5.Paralled Old收集器(老年代版本的多线程收集器,标记-整理)
6.CMS收集器 (以获取最短停顿时间为目标的收集器,注重用户体验应用)
7.G1收集器
8.ZGC收集器
1)cms垃圾收集器:cms是真正意义上的第一款,并发收集器 是一种标记-清除 算法
1.初始标记 暂停所有其他线程,记录直接与gc roots 相连的对象,速度快
2.并发标记 同时gc线程和用户线程同时执行
3.重新标记 修正并发标记期间产生变动的标记记录,停顿时间比初始标记长,比并发标记快
4.并发清除 开启用户线程,同时gc线程清扫
缺点:
1.对 CPU 资源敏感;
2.无法处理浮动垃圾;
3.它使用的回收算法-“标记-清除”算法会导致收集结束时会有大量空间碎片产生。
注:
如果cms收集器执行过程中,又触发了cms垃圾回收,则会走Serail串行化垃圾回收机制
2)G1垃圾收集器:是一款面向服务器的垃圾收集器,主要针对配备多颗处理器及大容量内存的机器. 以极高概率满足 GC 停顿时间要求的同时,还具备高吞吐量性能特征.(作用在新生代和老年代 jdk9及其之后默认使用此垃圾收集器)
划分为多个区域,每个区域都可以充当eden,survivor,old,humongous,其中 humongous 是专门为大对象准备的
采用标记-复制算法
响应时间与吞吐量兼顾
分成三个阶段:新生代回收、并发标记、混合收集
G1 收集器的运作大致分为以下几个步骤:
初始标记:
并发标记
最终标记
筛选回收
年轻代垃圾回收(Young Collection) => 年轻代垃圾回收 + 并发标记(Young Collection + ConCurrent Mark) => 混合垃圾回收(Mixed Collection) => 年轻代垃圾回收(Young Collection)
年轻代垃圾回收(Young Collection):
初始时,所有区域都处于空闲状态
创建了一些对象,挑出一些空闲区域作为伊甸园Eden区存储这些对象
当伊甸园Eden区需要进行垃圾回收的时候,挑出一个空闲的区域作为幸存者Survivor区,用复制算法从伊甸园Eden区复制存活的对象到幸存者Survivor区,需要STW
多次幸存者Survivor区 GC之后(最多15次),还存活的就会晋升至老年代
年轻代垃圾回收 + 并发标记(Young Collection + ConCurrent Mark):
当老年代的占用内存超过了45%的阈值之后,就会触发并发标记,这时不会STW,但是重新标记的时候,还是会需要进行STW的
并发标记之后,会有重新标记阶段解决漏标问题,此时需要暂停用户线程STW
等上述都完成之后就知道老年代有哪些对象存活,然后进入混合收集阶段。
此时不会对所有的老年代区域进行回收,而是根据暂停时间目标优先回收价值高(存活对象少)的区域(这也是Gabarge First名称的由来)
混合垃圾回收(Mixed Collection):
参与复制的有eden区、survivor区、old区,可能会执行多次,一次不一定能全部收集的完......
新生代的存活对象汇集到幸存者survivor区,或者满足进入老年代old区的就进入老年代old区
老年代的存活对象汇集到一个新的老年代old区
复制完成,内存得到释放。
如果一个对象太大了就会将此对象存入多个连续的humongous区
进入下一轮的新生代回收、并发标记、混合收集
如果并发失败(即垃圾回收的速度赶不上创建新对象的速度),触发Full GC
3)ZGC垃圾回收器
jdk11实验性引入,jdk15正式投入使用,使用方法启动参数添加:–XX:+UseZGC
优点:
1. 低延时,亚毫秒的最大暂停时间
2. 暂停时间不会随着堆、live-set 或 root-set 的大小而增加
3. 处理 TB 量级的堆(可以处理 8MB-16TB 的堆);
特点:
1.0 内存多重映射:
就是使用 mmap 把不同的虚拟内存地址映射到同一个物理内存地址上。
ZGC 为了更灵活高效地管理内存,使用了内存多重映射,把同一块儿物理内存映射为 Marked0、Marked1 和 Remapped 三个虚拟内存。
当应用程序创建对象时,会在堆上申请一个虚拟地址,这时 ZGC 会为这个对象在 Marked0、Marked1 和 Remapped 这三个视图空间分别申请一个虚拟地址,
这三个虚拟地址映射到同一个物理地址。
Marked0、Marked1 和 Remapped 这三个虚拟内存作为 ZGC 的三个视图空间,在同一个时间点内只能有一个有效。
ZGC 就是通过这三个视图空间的切换,来完成并发的垃圾回收。
2.0 染色指针:
总共有三种颜色,说明如下:
白色:本对象还没有被标记线程访问过。
灰色:本对象已经被访问过,但是本对象引用的其他对象还没有被全部访问。
黑色:本对象已经被访问过,并且本对象引用的其他对象也都被访问过了。
三色标记的过程如下:
初始阶段,所有对象都是白色。
将 GC Roots 直接引用的对象标记为灰色。
处理灰色对象,把当前灰色对象引用的所有对象都变成灰色,之后将当前灰色对象变成黑色。
重复步骤 3,直到不存在灰色对象为止。
三色标记结束后,白色对象就是没有被引用的对象(比如上图中的 H 和 G),可以被回收了。
9.JVM运行的三个模式
解释模式:*执行一行JVM字节码就编译一行为机器码
编译模式:*先将所有的JVM字节码一次编译为机器码,然后一次性执行所有机器码
混合模式:*依然使用解释模式执行代码,但是对于一些“热点”代码采取编译器模式执行,这些热点代码对应的机器码会被缓存起来,下次执行无需再编译。JVM一般采用混合模式执行代码
10.类的加载过程
加载,验证,准备,解析,初始化,使用,卸载
**加载:**在硬盘上查找并通过IO读入字节码文件,使用到类时才会加载,例如调用 类的main()方法,new对象等等
**验证:**校验字节码文件的正确性
**准备:**给类的静态变量分配内存,并赋予默认值
**解析:**将符号引用替换为直接引用,该阶段会把一些静态方法(符号引用,比如 main()方法)替换为指向数据所存内存的指针或句柄等(直接引用),这是所谓的静态链 接过程(类加载期间完成),动态链接是在程序运行期间完成的将符号引用替换为直接 引用,下节课会讲到动态链接
**初始化:**对类的静态变量初始化为指定的值,执行静态代码块
11.JVM调优
jsp 查看经常id
1.jmp查看整个jvm对象的数量及内存大小:jmp -histo
2.查看堆的情况:jmp -heap
3.jvisualVm
4.jstack 找出死锁 打印线程栈状态
5.jinfo -flags jvm运行的参数
6.jstat 可以看堆的各部分使用量,以及加载类的数量
垃圾回收统计 jstat -gc time<间隔事件 毫秒>
年轻代对象增长的速率
young gc的触发频率和每次耗时
每次young gc后由多少堆进入老年代
jvm调优主要:减少full gc ,垃圾再年轻代尽量的减少
垃圾收集器相关问题
1.什么是内存泄漏?
持有对象移植不释放,直到堆撑爆
2.如何排查线上内存泄漏,该如何排查?
答:在程序运行过程中,因为某些原因导致不需要使用的对象仍然占用jvm的空间,并且无法被回收,最终导致程序越来越来越大,从而出现oom。或者影响程序的性能
表现为:频繁full gc,内存不释放,老年代越来越大,年轻代居高不下,full gc卡顿
排查:使用jstat命令获取虚拟机内存区域的使用情况和gc情况,使用jmap来dump内存使用mat分析,定位有问题的类
3.jvm调优是什么?
答:减少full gc,垃圾在年轻代尽量的减少
3.如何排查线上cpu标高
4.堆为什么要分代?
提高效率。对象的寿命有长有短,寿命长的放在一个区,寿命短的放在另一个区。不同的区采用不同的垃圾收集算法。寿命短的区清理频次高一点,寿命长的区清理频次低一点。
5.如何判断一个类是无用的类
1)方法区主要回收的是无用的类
2)该类所有的实例都已经被回收,也就是 Java 堆中不存在该类的任何实例。
3)加载该类的 ClassLoader 已经被回收。
4)该类对应的 java.lang.Class 对象没有在任何地方被引用,无法在任何地方通过反射访问该类的方法。
6.HotSpot 为什么要分为新生代和老年代?
比如在新生代中,每次收集都会有大量对象死去,所以可以选择”标记-复制“算法,只需要付出少量对象的复制成本就可以完成每次垃圾收集。而老年代的对象存活几率是比较高的,而且没有额外的空间对它进行分配担保,所以我们必须选择“标记-清除”或“标记-整理”算法进行垃圾收集。
7.常见的垃圾回收器有哪些?
8.介绍一下 CMS,G1 收集器。
9.Minor Gc 和 Full GC 有什么不同呢?答:minor gc 主要清除的eden区,频率高,full Gc 整个堆,频率低
本文由 zzpp 创作,采用 知识共享署名4.0 国际许可协议进行许可
本站文章除注明转载/出处外,均为本站原创或翻译,转载前请务必署名
最后编辑时间为:
2024/09/12 09:10