目录
Serial Old 收集器 Serial 收集器的老年代版本
Parallel Old 收集器 Parallel Scavenge 收集器的老年代版本
1、如何判断对象是否死亡?
1.1、引用计数法
给对象中添加一个引用计数器:当有地方引用他的时候,计数器就+1,当引用失效,计数器就-1;如果计数器变为0,则说明对象不会被使用。这个方法实现简单,效率高,但是很难解决对象之间相互循环引用的问题。
1.2、可达性分析算法
将 “GC Roots” 的对象作为起点,向下搜索,节点所走过的路径称为引用链,当一个对象到 GC Roots 没有任何引用链相连的话,则证明此对象是不可用的,需要被回收。
2、介绍一下强引用、弱引用、软引用、虚引用
强引用:
当一个对象具有强引用,垃圾回收器不会回收这个对象。
如果是软引用,如果内存空间足够,垃圾回收器就不会回收它,如果内存空间不足了,就会回收这些对象的内存。软引用可用来实现内存敏感的高速缓存。
如果是弱引用,如果垃圾回收器线程在扫描的过程中发现了,则会直接回收,无论内存空间是否足够。由于垃圾回收器是一个优先级很低的线程, 因此不一定会很快发现那些只具有弱引用的对象。
虚引用:如果一个对象仅持有虚引用,它和没有任何引用一样,在任何时候都可能被垃圾回收。
3、如何判断一个常量是废弃常量?
如果当前没有任何 对象引用该常量,就说明常量就是废弃常量,如果这时发生内存回收的话而且有必要的话,废弃常量就会被系统清理出常量池了。
4、如何判断一个类是无用的类
类需要同时满足下面 3 个条件才能算是 “无用的类” :
- 该类所有的实例都已经被回收,也就是 Java 堆中不存在该类的任何实例。
- 加载该类的
ClassLoader
已经被回收。 - 该类对应的
java.lang.Class
对象没有在任何地方被引用,无法在任何地方通过反射访问该类的方法。
5、垃圾回收算法:
5.1、标记清除算法
该算法分为“标记”和“清除”阶段:首先标记出所有不需要回收的对象,对没有标记的对象进行统一回收。但是这种算法效率低,并且导致了严重内存碎片化,大对象无法存储。
5.2、复制算法
将内存分为大小相同的两块,每次使用其中的一块。当这一块的内存使用完后,就将还存活的对象复制到另一块去,然后再把使用的内存清理掉。解决了内存碎片化问题,但是内存空间仅为原来的一半。
5.3、标记整理算法
标记后不清理对象,而是将存活对象移向内存的一端。然后清除端边界外的对象
5.4、分代收集算法
GC堆划分为新生代(Young Generation)和老生代(Tenured/Old Generation)。根据各个年代的特点选择合适的垃圾收集算法。
比如在新生代中,每次收集都会有大量对象死去,所以可以选择”标记-复制“算法,成本低、效率高。而老年代的对象存活几率较高,而且没有额外的空间对它进行分配担保,所以我们必须选择“标记-清除”或“标记-整理”算法进行垃圾收集。
6、垃圾收集器
Serial 收集器
是一个单线程收集器,在进行垃圾收集工作的时候必须暂停其他所有的工作线程,简单高效,但是会阻塞其他用户线程。
ParNew 收集器
是 Serial 收集器的多线程版本,除了使用多线程进行垃圾收集外,其余行为(控制参数、收集算法、回收策略等等)和 Serial 收集器完全一样。
Parallel Scavenge 收集器
更加关注吞吐量,(吞吐量就是 CPU 中用于运行用户代码的时间与 CPU 总消耗时间的比值)尽可能地缩短垃圾收集时用户线程的停顿时间
Serial Old 收集器 Serial 收集器的老年代版本
Parallel Old 收集器 Parallel Scavenge 收集器的老年代版本
CMS 收集器
它依靠“标记-清除”算法实现,并发收集,在收集的时候,用户线程可以执行,减少了回收的停顿时间,也需要更多的cpu资源。
整个过程分为四个步骤:
- 初始标记: 暂停所有的其他线程,并记录下直接与GC root 相连的对象,速度很快 ;
- 并发标记: GC 和用户线程并发执行,进行可达性分析,找到需要回收的对象。用一个闭包结构去记录可达对象。但在这个阶段结束,这个闭包结构并不能保证包含当前所有的可达对象。因为用户线程可能会不断的更新引用域,所以 GC 线程无法保证可达性分析的实时性。所以这个算法里会跟踪记录这些发生引用更新的地方。
- 重新标记: 标记并发标记过程中产生的垃圾。重新标记阶段就是为了修正并发标记期间因为用户程序继续运行而导致标记产生变动的那一部分对象的标记记录,这个阶段的停顿时间一般会比初始标记阶段的时间稍长,远远比并发标记阶段时间短
- 并发清除: 开启用户线程,同时 GC 线程开始对未标记的区域做清扫。
主要优点:并发收集、低停顿。但是它有下面三个明显的缺点:
- 对 CPU 资源敏感;
- 无法处理浮动垃圾;
- 它使用的回收算法-“标记-清除”算法会导致收集结束时会有大量空间碎片产生。
G1收集器:
G1收集器把堆划分为多个大小相等的region,这个region可以根据需要来扮演新生代和老生代和Survivor空间,当我们标记好对象的时候,就把回收集中Region的存活对象复制到空的Region中,再清理掉整个旧Region的全部空间。
优势:
更精细的控制、可预测的停顿时间、内存碎片的控制、优先级处理
大对象会被存储到Humongous区域,G1大多数情况下会把这个区域当作老年代来看待。如果对象占用空间超过Region的容量,就会存放到N个连续的 Humongous Region 中
四个阶段,前三个和CMS一致:
最终是筛选回收(Live Data Counting And Evacuation),制定回收计划,选择多个Region构成回收集,把回收集中Region的存活对象复制到空的Region中,再清理掉整个旧Region的全部空间。需要STW.
Q & A
设置停顿时间的参数是什么?
-XX:MaxGCPauseMillis 默认值是 200 毫秒
G1收集器是怎么保证停顿时间可控的?
首先G1把内存区域分成了若干个相同大小的 Region 区,在执行回收的时候,根据标记阶段统计到的数据,计算出各个 Region 区的回收价值和成本,有了这些数据之后,就可以计算出回收哪几个Region价值最高,且符合用户预期的停顿时间。
谈谈jvm:
Java 虚拟机(Java Virtual Machine,JVM)是 Java 程序运行的核心部分,它是一种用于在不同平台上运行 Java 字节码的虚拟计算机。JVM 负责将 Java 字节码翻译为本地机器代码,以便在特定平台上执行 Java 程序。
以下是关于 JVM 的一些重要概念和特性:
1. Java 字节码:
Java 虚拟机执行的是 Java 字节码,而不是源代码。Java 源代码首先被编译成字节码,这是一种中间代码,不依赖于任何特定的硬件平台。
2. 类加载器(Class Loader):
类加载器负责加载 Java 字节码,并将其转换为运行时类。Java 虚拟机采用了双亲委派模型,根据一定的委派规则,类加载器可以将类的加载委托给父类加载器。这有助于保证类的唯一性和安全性。
运行时数据区域:
java运行时数据区域
线程私有的:
- 程序计数器
- 虚拟机栈
- 本地方法栈
线程共享的:
- 堆
- 方法区
- 直接内存 (非运行时数据区的一部分)
堆:所有的对象实例以及数组都要在堆上分配。
方法区:用于存储已被Java虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。
程序计数器:保存当前线程所正在执行的字节码指令的地址,保证线程切换后能恢复到正确的执行位置
java虚拟机栈:单位就是栈帧,栈帧中他又要存储,局部变量,操作数栈,动态链接
局部变量表:存放数据类型、对象引用
操作数栈:存放方法执行过程中产生的中间计算结果
动态链接:当一个方法要调用其他方法,需要将常量池中指向方法的符号引用转化为其在内存地址中的直接引用
虚拟机栈为虚拟机执行 Java 方法 (也就是字节码)服务,而本地方法栈则为虚拟机使用到的 Native 方法服务
直接内存
直接内存是一种特殊的内存缓冲区,并不在 Java 堆或方法区中分配的,而是通过 JNI 的方式在本地内存上分配的。
JVM的启动参数:
设置编码:-Dfile.encoding=UTF-8
1. -Xmx: 指定最大堆内存。 如 -Xmx4g。
2. -Xms: 指定堆内存空间的初始大小。 如 -Xms4g。 而且指定的内存大小,并不是操作系统实际分配的初始值,而是 GC 先规划好,用到才分配。 专用服务器上需要保持 –Xms 和 –Xmx 一致,
3. -Xmn: 设置年轻代大小,等价于 -XX:NewSize,使用 G1 垃圾收集器不应该设置该选项,在其他的某些业务场景下可以设置。官方建议设置为 -Xmx 的 1/2 ~ 1/4。
-XX:+UseG1GC:使用 G1 垃圾回收器。
-XX:+UseConcMarkSweepGC:使用 CMS 垃圾回收器。
#指定GC并行线程数-XX:ParallelGCThreads=4
#打印GC日志-XX:+PrintGCDetails-XX:+PrintGCDateStamps
#指定GC日志文件-Xloggc:gc.log
#指定Meta区的最大值 -XXMaxMetaspaceSize=2g(元空间)
#设置单个线程栈的大小 -Xss1m
-XX:±HeapDumpOnOutOfMemoryError:当 OutOfMemoryError 产生,即内存溢出(堆内存或持久代/元空间) 时,将内存信息放在 Dump 中。
-XX:HeapDumpPath=/usr/local/ 指定路径