1. 如何判断对象可以被回收?
目前是两种方式:
方式一:引用计数器
为每个对象创建一个引用计数,当有对象引用时,计数器+1,当引用释放时,计数器-1,所以,当计数器为0时,就认为可以被回收。
但这种算法,存在一个问题,存在循环引用的问题。存在内存溢出的风险 OOM
public static void main(String[] args){
One one = new One();
Tow tow = new Tow();
one.tow = tow;
tow.one = one;
one = null;
tow = null;
}
}
class One{
public Tow tow;
}
class Tow{
public One one;
}
方式二:可达性分析
从 GC Roots 开始向下搜索,搜索所走过的路径称为引用链。当一个对象到 GC Roots 没有任何引用链相连时,则证明此对象是不可用的,那么虚拟机就判断是可回收对象。
GC Roots的对象有:
- 虚拟机栈(栈帧中的本地变量表)中引用的对象,即方法中创建的对象
- 方法区中类静态属性引用的对象
- 方法区中常量引用的对象
- 本地方法栈中JNI(即一般说的Native方法)引用的对象
可达性算法中的不可达对象并不是立即死亡的,对象拥有一次自我拯救的机会。
对象被系统宣告死亡至少要经历两次标记过程:第一次是经过可达性分析发现没有与GC Roots相连接的引用链,第二次是在由虚拟机自动建立的Finalizer队列中判断是否需要执行finalize()方法。
当对象变成(GC Roots)不可达时,GC会判断该对象是否覆盖了finalize方法,若未覆盖,则直接将其回收。否则,若对象未执行过finalize方法,将其放入F-Queue队列,由一低优先级线程执行该队列中对象的finalize方法。执行finalize方法完毕后,GC会再次判断该对象是否可达,若不可达,则进行回收,否则,对象“复活”。
每个对象只能触发一次finalize()方法
由于finalize()方法运行代价高昂,不确定性大,无法保证各个对象的调用顺序,不推荐大家使用,建议遗忘它。
2. java的垃圾回收机制?
我们通常指的垃圾回收,指的就是回收堆的内存。
我们创建的对象都保存在堆中,java虚拟机通过垃圾自动回收机制,简称GC。
在java中,我们可以调用System.gc()来表示要进行垃圾回收,不过不建议使用,因为使用之后,虽然不会立即触发Full GC(堆内存全扫描),而是由虚拟机来决定执行时机,但是一旦执行,还是会停止所有的活动(stop the world),对应用影响很大。
一般建议,在一个对象不需要再被使用时,将其设置为null,这样GC虽然不会立即回收该对象的内存,但是会在下一次GC循环中被回收。
finalize()方法,它是在释放对象内存前,由GC调用,该方法有且仅被调用一次,一般不建议重写和调用该方法。手动调用finalize方法后,如果没有回收,则以后的GC不会再回收该对象了。
3. JVM的垃圾回收算法?
- 分代垃圾回收算法
主流的虚拟机大都采用分代收集算法,它根据对象存活周期的不同,而将内存划分为多块区域。一般就是我们耳熟能详的新生代和老年代,然后再各自采用不同的回收算法。
新生代(Eden),对象的存活率低,所以采用复制算法。
老年代(Old),对象的存活率高,所以采用标记清除或标记整理算法对象会优先分配到新生代,如果长时间存活或者对象过大会直接分配到老年代(新生代空间不够)。
-
复制算法
大多数对象在新生代中被创建,其中很多对象的生命周期很短。每次新生代的垃圾回收(又称Minor GC)后只有少量对象存活,所以选用复制算法,只需要少量的复制成本就可以完成回收。
对象复制算法是为了解决效率问题,它将内存一分为二,每次只使用其中一块,当这一块内容用完了,就将存活的对象复制到另一个块上,然后将另一块内存一次清理掉,这样回收的效率也就提升了,也不存在内存碎片的问题。
算法优点是回收效率高,不存在内存碎片,但是浪费内存一半的内存空间,另外在对象存活率高的情况下,采用复制算法,效率将会变低。
-
标记清除算法
标记清除算法是现在垃圾算法的思想基础,它将垃圾回收分为两个阶段:标记阶段和清除阶段。
标记清除算法的缺点:1,效率不高。2,该算法会产生不连续的内存碎片,当我们需要分配较大对象时,会因为无法找到足够的连续内存空间,而不得不再次提前触发垃圾回收,如果内存还是不够,则报内存不足异常。
-
标记压缩算法(标记整理)
标记压缩算法是老年代的一种回收算法, 首先,标记阶段跟标记清除算法一致;
区别在于清理阶段,为了避免内存碎片产生,所有的存活对象会被压缩到内存的一端
弊端:这个算法解决之前标记清除算法的碎片问题 但是标记和压缩的效率依然不高
3. 垃圾回收器有哪些?
做垃圾回收的时候,都有一个统一的特点,叫Stop the world.
往回收效率越来越高的方向来走的,垃圾回收的时间(stop the world)在变短
-
单线程回收器 Serial 串行
采用单个线程的方式来进行回收,效率一般。服务器是多核CPU,资源无法得到更好利用
-
多线程回收器 Parallel 并行 可以充分利用CPU资源
-
CMS回收器
- 初始化标记 这个时候会stop the world,但是由于我们只是标记GCRoot,所以花费的时间很短
- 并发标记 一边可以继续往下跟踪,做可达性分析,相比比较耗时 一边可以让程序继续运行,可能重新创建对象,也可能创造垃圾
- 重新标记 处理在并发标记过程中,再次产生新的垃圾,stop the world
- 并发回收 一边针对我们刚才的垃圾对象进行回收一边程序继续运行
-
G1垃圾回收器 Garbage First 垃圾优先
将内存划分多个块 ,每个块再独立进行回收
内存模型是实际不分代,但是逻辑上是分代的。在内存模型中,对于堆内存就不再分老年代和新生代,而是划分成一个一个的小内存块,叫做Region,每个 Region可以隶属于不同的年代。
G1的GC分为5个阶段:
1. 初始标记 标记出GCRoot直接引用的对象。STW
2. 标记Region,通过RSet标记出上一个阶段标记的Region引用到的Old区Region。
3. 并发标记阶段:跟CMS的步骤是差不多的。只是遍历的范围不再是整个Old区,而只需要遍历第二步标记出来的Region。
4. 重新标记: 跟CMS中的重新标记过程是差不多的。
5. 垃圾清理:与CMS不同的是,G1可以采用拷贝算法,直接将整个Region中的对象拷贝到另一个Region。而这个阶段,G1只选择垃圾较多的Region来清理,并不是完全清理。CMS的核心算法就是三色标记。 三色标记:是一种逻辑上的抽象。将每个内存对象分成三种颜色: 黑色:表示自己和成员变量都已经标记完毕。 灰色:自己标记完了,但是成员变量还没有完全标记完。 白色:自己未标记完。 CMS通过增量标记increment update 的方式来解决漏标的问题。
4. 常见JVM参数?
-
Xms 2g:堆初始值 2g;
-
Xmx 4g:堆最大内存 4g;
-
Xmn 新生代堆最大可用值
-
XX:NewRatio=4:设置年轻的和老年代的内存比例为 1:4;
-
XX:SurvivorRatio=8:设置新生代 Eden 和 Survivor 比例为 8:2;
-
设置最大堆内存
参数: -Xms5m -Xmx20m -XX:+PrintGCDetails -XX:+UseSerialGC -XX:+PrintCommandLineFlags -
设置新生代与老年代优化参数
-Xmn 新生代大小,一般设为整个堆的1/3到1/4左右
-XX:SurvivorRatio 设置新生代中eden区和from/to空间的比例关系n/1 -
设置新生代比例参数
参数: -Xms20m -Xmx20m -Xmn1m -XX:SurvivorRatio=2 -XX:+PrintGCDetails -XX:+UseSerialGC
在实际工作中,应该根据系统的特点做出合理的配置,基本策略:尽可能将对象预留在新生代,减少老年代的GC次数。
在JVM启动参数中,可以设置跟内存、垃圾回收相关的一些参数设置,默认情况不做任何设置JVM会工作的很好,但对一些配置很好的Server和具体的应用必须仔细调优才能获得最佳性能。通过设置我们希望达到一些目标:
1.GC的时间足够的小
2.GC的次数足够的少
3.发生Full GC的周期足够的长
5. 查看JVM运行情况常用的命令?工具?
查看JVM(Java虚拟机)运行情况常用的命令?
- 查找Java进程
- 使用ps命令结合grep查找Java进程,例如:ps -ef | grep java 或 ps -ef | grep tomcat。
- 使用jps命令显示当前系统中所有Java进程的信息,例如:jps -l。
- 查看JVM状态信息
- 使用jstat命令监视JVM的各种状态信息,如内存使用情况、GC情况等。例如:jstat -gcutil PID 2s,其中PID是JVM进程的ID,2s表示每2秒更新一次数据。
- 使用jmap命令生成JVM的堆转储快照,用于分析内存使用情况。例如:jmap -heap PID。
- 使用jstack命令生成JVM的线程转储快照,用于分析线程问题,特别是死锁等。例如:jstack -l PID。
- 调整JVM配置和运行参数
- 使用jinfo命令显示JVM的配置信息,并可以修改运行时的Java进程的运行参数。例如:jinfo -flag MaxPermSize PID。
- 图形化监控工具
- 使用jconsole,这是一个Java GUI监视工具,可以以图表化的形式显示各种数据,并可通过远程连接监视远程的服务器VM。
- 其他命令
- jcmd:向正在运行的Java进程发送诊断命令。
- jhat:与jmap搭配使用,用来分析jmap生成的dump文件。它内置了一个微型的HTTP/HTML服务器,生成dump的分析结果后,可以在浏览器中查看。
- 命令参数详解
-对于jstat、jmap等命令,通常可以通过添加不同的参数选项来获取不同的信息。
-例如,jstat的-gc参数用于显示垃圾回收器的状态信息,-class参数用于显示类加载器的状态信息等。
查看工具?
Jdk1.8自带工具:
- Jconsole
- JVisualVM 选择Tomcat服务的端口进行监控
- jProfiler整合idea和eclipse工具。
- Spring Boot Admin 监控和管理Spring Boot应用程序的开源工具。
- 阿里的arthas,实时监控和诊断:Arthas允许开发者实时监控Java应用的运行状态,包括类加载信息、JVM性能指标、线程堆栈等,帮助开发者快速定位问题
6. Arthas常用命令
-
JVM相关命令
dashboard
功能:查看JVM的总体信息,包括线程、内存和运行环境等。
thread
功能:查看某个线程或所有线程的信息,包括线程ID、线程名、线程优先级、CPU使用率等。
示例:
thread:查看当前JVM的所有线程信息。
thread -n 5:查看前5个最繁忙的线程信息。
thread [id]:查看指定ID的线程堆栈信息。
jvm
功能:显示当前JVM的详细信息,如内存管理器、垃圾收集器、类加载等。
示例:直接输入jvm命令即可查看。
memory
功能:查看JVM的内存分布和使用情况。
示例:直接输入memory命令即可查看。 -
监控类命令
monitor
功能:监控指定类中方法的执行成功/失败情况,统计方法被调用的次数等。
示例:monitor -c 5 com.example.MyClass myMethod 每5秒统计一次myMethod方法的调用次数。
watch
功能:“精确”监控某指定类中方法的参数变化/返回值变化情况。
示例:watch com.example.MyClass myMethod “{params,returnObj,throwExp}” 监控myMethod方法的参数、返回值和异常。
trace
功能:打印某方法调用路径,并输出每个节点的耗时。
示例:trace com.example.MyClass myMethod 跟踪myMethod方法的调用路径和耗时。
stack
功能:获取当前方法的被调用路径。
示例:stack com.example.MyClass myMethod 查看myMethod方法的调用堆栈。
tt
功能:“精确”监控某指定类中方法的参数变化/返回值变化情况,并可以记录方法调用的状态。
示例:tt -t com.example.MyClass myMethod 记录myMethod方法的调用状态。
profiler
功能:生成火焰图,用于分析CPU和内存使用情况。