算法 自动采集列表(【干货】如何确定是垃圾?java是怎么确定的? )
优采云 发布时间: 2021-12-28 18:18算法 自动采集列表(【干货】如何确定是垃圾?java是怎么确定的?
)
一、如何判断是垃圾?1、引用计数法
如果对象没有与之关联的引用,则计数器为 0 的对象是可回收对象。(目前使用的是python)
优点:判断效率高,实现简单。
缺点:不完全准确,无法回收循环引用的对象,容易出现内存泄漏。
2、可访问性分析(根可达性)
以一系列GC Roots对象为起点,从这些根节点开始向下搜索,一路搜索,称为引用链接。当一个对象在没有任何引用链的情况下连接到 GC Roots 时,就意味着该对象不可用。
优点:解决了相互循环引用的问题。
注意:不可达对象不等同于可回收对象。不可达对象至少需要经过两次标记过程才能成为可回收对象。你可以通过 finalize() 来拯救自己。
3、GC Roots 对象
GC Roots 对象包括:
虚拟机栈帧中局部变量表引用的对象;
方法区中的类静态属性引用;
方法区常量引用的对象;
JNI在本地方法栈中引用的对象;
同步锁持有的所有对象等;
jvm中跨代引用的对象等;
……还有其他种类,常用的前四种。
二、垃圾采集
算法?
Java自动回收内存,C、C++等必须手动回收。
1、复制算法
根据内存容量将内存分成大小相等的两块,每次使用一块。在这个块填满后,将幸存的块复制到另一个块,并清空它使用的内存。
优点:实现简单,不易产生碎片;
缺点:可用内存被压缩到原来的一半,如果存活的对象很多,这个算法的效率会大大降低。
2、标记-扫描算法(Mark-Sweep)
标记要回收的对象,回收被标记对象占用的空间。
缺点:内存碎片严重;
3、Mark-Compact
标记要回收的对象。标记后,对象没有明确标记,而是将幸存的对象移动到内存的一端,然后清除边界外的对象。
优点:不碎片化;
4、 分代采集
算法
目前JVM大多采用【新生代、老年代、永久代】
根据对象生存的不同生命周期,将内存划分为不同的域。一般情况下,垃圾回收主要是回收堆空间(因为几乎大部分对象都在堆空间,特殊情况逃逸分析:在栈上分配),所以将堆划分为新生代(1/3),老年代 (2/3).
老年:大物件直接放在老年;长寿之物进入老年;
每次只需要回收少量对象,存活率高且相对稳定。所以老年选择标记排序算法或标记清除算法。老年代的垃圾回收叫做Major GC;
新生代:储存新生物体。对象生死存亡,大量对象被回收,少数存活,所以复制成本低,所以新一代选择复制算法。新一代的垃圾回收被称为 Minor GC(copy-empty-swap)。
永久代:方法区的永久代,用于存放类、常量、方法描述等。永久代的回收主要是废弃的常量和无用的类。垃圾少,收益一般也小,所以垃圾回收主要是回收堆空间。1.8 之后称为元空间。
java内存模型
注意:Minor GC 通常在 Major GC 之前执行。Minor GC 会频繁触发,而 Major GC 不会。当无法找到足够大的连续空间分配给新创建的较大对象时,会提前触发Major GC;Major GC速度一般比Minor GC慢10倍以上;
Full GC:就是清理整个堆空间。
触发条件:
①手动使用System.gc()
②老年空间不足;
③方法区空间不足;
④Minor GC后,要移动或分配的对象的大小大于老年代的可用空间。
总结:分代回收算法是根据不同区域对象的生命周期特征选择不同的回收算法;
垃圾回收主要回收堆内存;
幸存者区晋升的年龄门槛为幸存者区的15%或50%有两种情况。
三、GC 性能测量指标1、 吞吐量:
这里测量的吞吐量是指应用程序花费的时间与系统总运行时间的比率。我们可以根据这个公式计算GC的吞吐量:系统总运行时间=应用耗时+GC耗时。如果系统运行100分钟,GC耗时1分钟,则系统吞吐量为99%。一般GC的吞吐量不能低于95%。
2、暂停时间:
指垃圾采集
器运行时应用程序的暂停时间。对于串口采集
器,暂停时间可能比较长;在使用并发采集
器时,因为垃圾采集
器和应用程序交替
运行时,程序的暂停时间会更短,但其效率很可能不如独占垃圾采集
器,系统的吞吐量很可能会下降。
3、 垃圾回收频率:
一般来说,垃圾采集
的频率越低越好。增加堆内存空间可以有效降低垃圾回收的频率,但同时也意味着要积累更多的对象进行回收,最终会增加回收过程中的暂停时间。所以我们需要适当增加堆内存空间,以保证正常的垃圾回收频率。
四、垃圾采集
器
目前7大主流:
新生代采集器
:Serial、ParNew、Parallel Scavenge;
老年采集
器:Serial Old、Parallel Old、CMS;
整桩采集
器:G1;
下面一一介绍这些采集器的特点和功能:
1、串行(单线程,复制算法,新一代)
当 Serial 采集
垃圾时,它必须挂起所有其他工作线程,直到垃圾采集
结束。对于单CPU,没有线程交互开销,效率高。
因此,Serial是Client模式下运行的java虚拟机默认的新一代垃圾回收器。
2、ParNew(Serial的并行多线程版本,复制算法,新一代)
ParNew 除了使用多个线程进行垃圾回收之外,还有和 Serial 相同的行为,它也会挂起所有的工作线程。
ParNew 默认打开具有相同 CPU 数量的线程。您可以使用参数 -XX:ParallelGCThreads 限制线程数。它是服务器模式下新一代虚拟机的默认垃圾采集
器。
3、Parallel Scavenge(并行多线程版本,复制算法,新一代)
专注于程序可以实现的可控吞吐量,通过自适应调整策略来提高用户的
经验。是1.8默认的新一代采集
器。
吞吐量=cpu运行用户代码时间/cpu总消耗时间
自适应调整策略:
Parallel Scavenge采集
器可以配合自适应调整策略将内存管理的调优任务委托给虚拟机来完成。你只需要设置基本内存数据(比如-Xmx设置最大堆),然后使用MaxGCPauseMillis参数(更多关于最大暂停时间)或GCTimeRatio参数(更多关于吞吐量)来设置优化目标虚拟机,具体参数的详细调整工作由虚拟机完成。
1),java -XX:+PrintFlagsFinal,可以看到1.8 默认是UseParallelGC
ParallelGC 默认为 Parallel Scavenge(新生代)+ Parallel Old(老年代)
** 2),自适应调整策略也是Parallel Scavenge采集
器和ParNew采集
器的重要区别。**
4、Serial Old(单线程,标签排序算法,年老)
主要运行在Client模式,java虚拟机默认的老年代垃圾采集
器,
服务器模式下的两个目的:
①:jdk1.5之前与新一代Parallel Scavenge配合使用;
②:作为老年代CMS采集
器的备用垃圾采集
方案。
5、Parallel Old (Parallel Multithreading, Marking Sorting Algorithm, Old Age)
如果还考虑老年代的吞吐量,可以考虑搭配新一代Parallel Scavenge使用。
6、CMS(Concurent Mark Sweep)(并发多线程,标记清除算法,老年代)
主要目的是获得最短的垃圾回收暂停时间,可以提高交互性高的程序的用户体验。是老年代唯一的垃圾回收器,有标记清除算法,而不是标记排序算法。
缺点:cpu敏感、浮动垃圾、内存碎片
CMS分为四个阶段:
1)初始标记(暂停):只标记GC Roots可以直接关联的对象,快速,挂起所有工作线程;
2) 并发标记(concurrency):GC Roots 跟踪,与用户线程一起工作;
1) 重新标记(暂停):在并发标记期间,对因程序运行而改变标记的对象部分进行标记,所有工作线程暂停;
1)并发清理(concurrent):清理GC Roots不可达的对象,和用户线程一起工作。
所以总的来说,CMS采集
器的内存回收和用户线程是并发执行的。例如:Web 程序、B/S 服务。
7、G1(垃圾优先)
G1最突出的改进:
1)基于标签排序算法,不存在碎片;
2) 可以非常精准的控制暂停时间,可以在不牺牲吞吐量的情况下实现低暂停回收,让用户可以在M毫秒的时间段内明确指定垃圾采集
所花费的时间。时间不得超过 N 毫秒。
G1是为了避免整个区域的垃圾回收,而是将java堆划分为多个固定大小的独立区域,并跟踪这些区域的垃圾堆积程度,在后台维护一个优先级列表,每次根据允许回收时间 回收最多垃圾的区域。
8、垃圾采集
器的组合
以上7种垃圾采集
器可以组合使用,有连接的可以使用。
注意几个概念:单线程、并行、并发是不一样的。
并行(Parallel):指多个垃圾回收线程并行工作,但此时用户线程仍处于等待状态;例如ParNew、Parallel Scavenge、Parallel Old;
Concurrent:指用户线程和垃圾回收线程同时执行(但不一定并行,可能交替执行),如CMS、G1;
CMS和G1同时打标:采用三色打标:分别为白、灰、黑。三色标记的最大优点是可以异步执行,从而可以以最小的中断时间或根本不中断为代价来执行整个GC。
并发打标容易出现产品缺标问题。CMS 从根扫描并比较 G1 快照方法的差异。具体在此不展开。
五、安全点和安全区1、安全点
任何时候都不可能进行GC。当系统需要进行垃圾回收时,业务线程不会立即停止。可想而知,立即停车可能会出现问题。为了准确、安全地回收内存,JVM 处于安全点。回收将在一个时间点进行,
正是业务线程按照一定的策略轮询和检查这个变量,一旦发现是安全点(Safe Point),就主动挂起,这样当JVM到达安全点时,一个安全准确的可以实现GC。
安全点主要设置在以下位置:
1)。循环结束
2)。在方法返回之前
3)。调用方法调用后
4)。抛出异常的位置
2、安全区
如果用户线程休眠并等待,它无法主动检测变量到安全点。显然,JVM 不能等待程序唤醒。这时候就需要一个安全的区域。
安全区是指引用关系不会改变的一段代码。GC在这个区域的任何地方都是安全的,安全区域可以看作是安全点的延伸。线程在执行安全区的代码时,首先识别出进入了安全区,这样GC就不需要进入安全区的行层。当线程想要离开安全区时,它会检查 JVM 是否完成了 GC Roots 枚举。如果完成继续执行,如果没有完成,请等到收到可以安全离开的信号。