解决方案:垃圾回收算法以及常见垃圾收集器简介
优采云 发布时间: 2022-11-26 03:24解决方案:垃圾回收算法以及常见垃圾收集器简介
垃圾采集
算法及常见垃圾采集
器介绍
文章目录
前言
比如:作为一个java程序员,JVM会帮你进行垃圾回收,那么JVM是怎么回收的呢?常见的垃圾回收算法和垃圾回收器有哪些?它们是如何工作的?
1. 垃圾采集
算法 1.1 分代采集
理论
根据年轻代或老年代的不同,选择不同的垃圾回收算法。年轻代一般采用复制算法,老年代一般采用标记清除算法或标记排序算法。
1.2 标记-复制算法
内存排序前:首先将内存分为used(保留内存)和unused(可用内存、可回收内存、存活对象(通过gc根可达性算法判断哪些对象存活))两部分,(从eden从park到幸存者区);
内存排序后:将已使用(保留内存)和未使用(可用内存、可回收内存、存活对象)复制交换,回收未使用的可回收内存;
缺点:每次内存回收回收一半内存空间,浪费空间。年轻代使用复制算法,老年代不使用复制算法。
1.3 标记-扫描算法
标记存活的对象,清除未标记(not alive)的对象。
缺点: 1.效率问题,如果标记的对象太多,效率不高;2、空间问题,标记清除后会产生大量不连续的碎片(内存空间);
1.4 标记整理算法
标记存活对象,让存活对象移动到可回收对象(不清除垃圾对象,直接将非垃圾对象移动到垃圾对象,就像赋值一样),如果可回收对象中没有存活对象,然后直接清除它们可回收对象;(write barrier模式同步修改内存地址)
2.垃圾采集
器
如果说回收算法是内存回收的方法论,那么垃圾回收器就是内存回收的具体实现。
常见的垃圾采集
器有Serial、ParNew、Parallel、CMS、Serial Old、Parallel Old、G1、ZGC。
为什么有这么多垃圾采集
器?因为目前还没有任何场景都可以使用的采集
器,所以我们要根据具体的场景选择适合自己的垃圾采集
器。
2.1 串行采集
器
串行(serial)采集
器是最基本也是最古老的垃圾采集
器。它是一个单线程垃圾采集
器,在垃圾采集
期间暂停所有其他工作线程(“Stop The World”或简称 STW)。新生代采用复制算法,老年代采用标记-排序算法。可以回收的内存区域在几十兆到几百兆之间,不是很大,也不再使用了。使用参数 -XX:+UseSerialGC -XX:+UseSerialOldGC
缺点:效率低,会给STW中的用户带来不好的用户体验;
优点:简单高效(相对于其他单线程采集
器);
Serial Old采集
器是Serial采集
器的老版本,也是单线程采集
器。它主要有两个用途:一是在JDK1.5及更早版本中与Parallel Scavenge采集
器配合使用,二是作为CMS采集
器的备份方案。
2.2 并行清扫采集
器
并行采集
器实际上是串行采集
器的多线程版本。除了使用多线程进行垃圾采集
外,其余行为(控制参数、采集
算法、回收策略等)与Serial采集
器类似。默认采集
线程数与 CPU 核心数相同。当然采集
线程数也可以通过参数(-XX:ParallelGCThreads)指定,但一般不建议修改。Parallel Scavenge采集
器侧重于吞吐量(CPU的高效利用),其STW时间比较长,用户体验不好。CMS等垃圾采集
器的关注点更多的是用户线程的停顿时间(提升用户体验)。所谓吞吐量就是CPU中运行用户代码所花费的时间与CPU总消耗时间的比值。新生代采用复制算法,老年代采用标记-排序算法。使用参数-XX:+UseParallelGC(年轻代),-XX:+UseParallelOldGC(老年代)
Parallel Old 采集
器是 Parallel Scavenge 采集
器的老年代版本。使用多线程和“标记和排序”算法。在注重吞吐量和CPU资源的情况下,Parallel Scavenge采集
器和Parallel Old采集
器(JDK8默认的新生代和老年代采集
器),Parallel采集
器和CMS(老年代才有)采集
器可以优先使用 没有一起使用它的方法。
2.3 ParNew采集
器
ParNew采集
器其实和Paralle采集
器非常相似(ParNew用于新生代),区别(ParNew+CMS)主要是可以和CMS(用于老年代)采集
器结合使用。新生代采用复制算法,老年代采用标记-排序算法。使用参数-XX:+UseParNewGC。
2.4 CMS 采集
器
CMS(Concurrent Mark Sweep)采集
器是一种以获取最短恢复停顿时间为目标的采集
器,非常注重用户体验。
操作过程大致如下:
2.4.1 初始标记
挂起所有其他线程(STW,如果没有STW机制,初始标记阶段会产生连续的对象,无法完成初始标记),记录gc roots可以直接引用的对象,以及速度很快;
2.4.2 并发标记
无需停止应用程序线程。应用线程和CMS线程同时运行,可能导致被标记对象的状态发生变化;(如果内存变大,整个堆内存会变大,标记时间也会变长,STW时间也会变长,用户体验不好。)缺少标签(下面会有解决方法)。
2.4.3 重新贴标签
它还会STW,重新标记那些在并发标记中状态发生变化的对象;(三色标增量更新)
2.4.4 并发清理
没有被标记的对象(垃圾对象)被清除,应用线程和CMS线程同时运行(因为应用线程也在运行,会产生新的对象,如果这些引用会被标记为黑色)它们没有被标记,黑色不会被扫描后变成浮动垃圾,在下一轮被清理(三色标记))。
2.4.5 并发复位
清理上面步骤标记的对象,方便下次重新标记。
如果ParNew采集
器的执行时间为1s,那么CMS采集
器的执行时间就会大于1s。为什么?因为CMS采集
器在执行过程中会分配一部分资源(CPU)给应用线程执行,但是没有STW(用户在初始标记和重新标记时会感觉到STW),用户几乎没有感觉等了很久。
CMS采集
器的优点:并发采集
、低暂停。
CMS采集
器的缺点:
(1). 会和CPU竞争资源(应用线程和CMS线程会同时运行);
(2). 浮动垃圾(并发标记和并发清理时会产生新的垃圾,只能被下一次gc处理);
(3). 使用mark-clear算法会产生大量的空间碎片,但是JVM可以通过XX:+UseCMSCompactAtFull采集
参数让jvm清除mark然后整理;
(4). 在并发标记和并发清理的过程中,会出现同时回收和运行的情况。可能在回收完成之前会再次触发full gc(垃圾会在上次垃圾回收完成之前再次触发),是并发失败(concurrent mode failure),会stop the world,使用Serial old collector来回收。
2.4.6 CMS相关核心参数
1.-XX:+UseConcMarkSweepGC:启用cms;
2. -XX:ConcGCThreads:并发GC线程数;
3. -XX:+UseCMSCompactAtFull采集
: FullGC后做压缩(减少碎片);
4. -XX:CMSFullGCsBeforeCompaction:FullGC后压缩多少次,默认0,表示每次FullGC后压缩;
5. -XX:CMSInitiatingOccupancyFraction:当老年代的使用率达到这个比例(默认是92,是一个百分比)的时候会触发FullGC;
6、-XX:+UseCMSInitiatingOccupancyOnly:只使用设置的恢复阈值(-XX:CMSInitiatingOccupancyFraction设置的值),如果不指定,JVM只会在第一次使用设置的值,之后会自动调整;
7. -XX:+CMSScavengeBeforeRemark:在CMS GC之前启动一次minor gc,目的是减少老年代对新生代的引用,减少CMS GC标记阶段的开销。一般CMS 80%的GC时间都在标记阶段;
" />
8、-XX:+CMSParallellnitialMarkEnabled:表示初始标记时多线程执行,缩短STW;
9、-XX:+CMSParallelRemarkEnabled:remarking时多线程执行,缩短STW;
2.5 G1 垃圾采集
器 2.5.1 G1 垃圾采集
器简介
在 JDK9 中,默认的垃圾采集
器变成了 G1。在 JDK9 中,CMS 仍然可以使用,但不推荐使用。在JDK8中,也可以使用G1,但不推荐。G1采集
器(-XX:+UseG1GC),G1(Garbage-First)是面向服务器的垃圾采集
器,主要针对配备多处理器和大容量内存的机器,同时具有高吞吐量的特点。
G1将java堆内存划分为大小相等的独立区域(Regions),JVM最多可以有2048个Region。如果一个内存是4G(4096M),那么每个Region平均就是2M。当然也可以通过参数“-XX:G1HeapRegionSize”手动指定Region大小,但建议使用默认的计算方式。
G1保留了新生代和老年代的概念,但不再是物理隔离。它们都是(可以是不连续的)Region 集合。比如一个Region之前可能是新生代,如果Region被垃圾回收了,后面可能又变了。变成了晚年。
默认情况下,新生代占堆内存的5%。如果heap size为4096M,那么young generation大约占用200MB内存,对应100个Region左右。可以通过“-XX:G1NewSizePercent”设置新生代的初始比例。在系统运行的过程中,JVM会不断的给年轻代增加Region,但是年轻代的最大比例不会超过60%,可以通过“-XX:G1MaxNewSizePercent”来调整。年轻代中Eden和Survivor对应的region也和之前一样。默认值为 8:1:1。假设新生代有1000个region,eden区有800个,s0有100个,s1有100个。
G1和其他垃圾采集
器的区别在于对大对象的处理。G1有一个Region叫做Humongous区,专门用来分配大对象,而不是直接进入老年代。在G1中,如果一个大对象超过了Region的50%,就会被放到Humongous区域。如果一个对象的内存是1.5M,就直接放到Humongous里面。如果一个对象的内存是6M,那么它会被放置在三个连续的Humongous区。
2.5.2 G1采集器运行过程
初始标记:挂起所有其他线程,记录gc roots直接引用的对象,非常快(STW);
并发标记:同CMS并发标记;
Final markup: remark with CMS (will STW);
Filter recovery(类似于CMS并发清理,只是STW):根据用户预期的GC暂停STW时间指定回收(可以通过JVM参数指定:-XX:MaxGCPauseMills),比如old generation中1000个region满了,但是因为按照预期的停顿时间,这次恢复可能是200毫秒(如果没有设置参数-XX:MaxGCPauseMills,默认是200ms),通过计算可知,恢复800的可能只需要200毫秒regions,所以会回收800个region(采集
Set,待回收的采集
),剩下的200个region会在下次垃圾回收时回收,尽量把GC停顿时间控制在我们设定的时间范围内。不管是年轻一代还是老一代,回收算法主要使用复制算法(G1整体上是一种标记算法),将一个区域中存活的对象复制到另一个区域中,不会像CMS那样被回收 因为有很多内存碎片需要回收整理一次,G1使用复制算法回收几乎没有内存碎片。G1 采集
器在后台维护一个优先级列表。每次根据允许回收时间,优先回收价值最高的区域。比如一个region可以在200ms回收10M垃圾,另一个region可以在50ms回收10M垃圾。在有限的情况下,G1当然会优先回收后面的区域。不会像CMS那样被回收 因为有很多内存碎片需要整理一次,所以G1使用复制算法回收几乎没有内存碎片。G1 采集
器在后台维护一个优先级列表。每次根据允许回收时间,优先回收价值最高的区域。比如一个region可以在200ms回收10M垃圾,另一个region可以在50ms回收10M垃圾。在有限的情况下,G1当然会优先回收后面的区域。不会像CMS那样被回收 因为有很多内存碎片需要整理一次,所以G1使用复制算法回收几乎没有内存碎片。G1 采集
器在后台维护一个优先级列表。每次根据允许回收时间,优先回收价值最高的区域。比如一个region可以在200ms回收10M垃圾,另一个region可以在50ms回收10M垃圾。在有限的情况下,G1当然会优先回收后面的区域。优先考虑回收价值最高的地区。比如一个region可以在200ms回收10M垃圾,另一个region可以在50ms回收10M垃圾。在有限的情况下,G1当然会优先回收后面的区域。优先考虑回收价值最高的地区。比如一个region可以在200ms回收10M垃圾,另一个region可以在50ms回收10M垃圾。在有限的情况下,G1当然会优先回收后面的区域。
2.5.3 G1采集器特点
并行和并发:G1可以充分利用CPU和多核环境的硬件优势,使用多个CPU(CPU或CPU核心)来缩短Stop-The-World暂停时间。其他一些采集
器原本需要停止Java线程执行GC动作,但G1采集
器仍然可以让Java程序继续并发执行。
分代采集
:虽然G1可以独立管理整个GC堆而不需要其他采集
器的配合,但是它仍然保留了分代的概念。
空间整合:与CMS的“mark-clean”算法不同,G1是一个整体基于“mark-sort”算法的采集
器;它是在本地基于“复制”算法实现的。
可预测的暂停:这是 G1 相对于 CMS 的另一大优势。减少暂停时间是 G1 和 CMS 共同关心的问题。不过,除了追求低停顿之外,G1 还可以建立一个可预测的停顿时间模型,允许用户或明确指定在 M 毫秒的时间段内完成垃圾采集
(由参数“-XX:MaxGCPauseMillis”指定) .
毫无疑问,能够由用户指定预期的停顿时间是G1采集
器非常强大的功能。设置不同的预期停顿时间可以让G1在不同的应用场景下,在关注吞吐量和关注延迟之间取得最佳平衡。不过,这里设定的“期望值”一定要切合实际,不能异想天开。毕竟G1是冻结用户线程复制对象,停顿时间再低也得有个限度。它的默认暂停目标是 200 毫秒。一般来说,回收阶段占用几十到一百甚至接近200毫秒是正常的,但是如果我们把暂停时间调整到一个很低的值,比如设置为20毫秒,最有可能的结果是因为暂停目标时间太短,每次选择的采集
集合只占用堆内存的一小部分,采集
器的采集
速度逐渐跟不上分配器的分配速度,导致垃圾堆积。采集
器很可能一开始可以从空闲堆内存中获得一些喘息时间,但是应用程序运行时间长了就不行了,最终堆满会导致Full GC降低性能,所以预期的停顿时间通常设置为一两百毫秒或者两三百毫秒会比较合理。
2.5.4 G1垃圾采集
分类
YoungGC:YoungGC并不是说当现有的Eden区满了就立即触发。当G1触发YoungGC时,会判断触发时间是否接近设定的时间(默认为200ms)。如果没有关闭,则不会立即触发。YoungGC会不断的给Eden区增加空间(默认是5%,最大不会超过60%),只有在关闭的时候才会触发YoungGC。
MixedGC:当老年代占用堆内存的比例达到45%时(可以通过参数-XX:InitiatingHeapOccupancyPercent设置),触发MixedGC,回收所有Young和部分Old(决定垃圾回收的优先级)按照预期的GC停顿时间顺序在old区)和大对象区(Humongous area)。(MixedGC会回收 G1的Humongous区)
FullGC:在做复制算法的过程中(对内存空间要求比较高),需要将每个region中存活的对象复制到其他region中。如果在复制过程中发现没有足够的空区域来承载复制的对象,则会失败。触发Full GC,触发Full GC会停止系统程序,然后使用单线程进行标记、清理、压缩,为下一次MixedGC腾出一批region。这个过程非常耗时。
2.5.5 G1采集器参数配置
-XX:+UseG1GC:使用G1采集
器;
-XX:ParallelGCThreads:指定GC工作的线程数;
-XX:G1HeapRegionSize:指定分区大小(1MB~32MB,且必须是2的N次方),默认整个堆分为2048个分区
-XX:MaxGCPauseMillis:目标暂停时间(默认200ms);
-XX:G1NewSizePercent:新生代内存的初始空间(默认为整个堆的5%);
-XX:G1MaxNewSizePercent:新生代的最大内存空间;
-XX:TargetSurvivorRatio:Survivor区域的填充容量(默认50%)。Survivor区的一组物体(年龄1+年龄2+年龄n多个年龄的物体)总和超过Survivor区的50%。此时,年龄为n(含)及以上的对象将被放入老年代;
-XX:MaxTenuringThreshold:最大年龄阈值(默认15);
-XX:InitiatingHeapOccupancyPercent:老年代占用的空间达到整个堆内存的阈值(默认45%),则进行新生代和老年代的混合采集
(MixedGC)。比如我们前面提到的heap,默认有2048个region。如果有接近1000个region,每个region都是老年代region,可能会触发MixedGC;
-XX:G1MixedGCLiveThresholdPercent (default 85%) 当区域中的存活对象低于该值时,该区域将被回收。如果这个值超过这个值,说明存活对象太多,回收意义不大;
-XX:G1MixedGCCountTarget:指定一次回收过程中筛选回收的次数(默认8次)。在最后的筛选回收阶段,可以先回收一段时间,然后暂停回收,恢复系统运行,稍后再开始回收,这样就不会让系统单次停太久;
-XX:G1HeapWastePercent (default 5%):gc过程中腾出的region是否足够阈值。混合回收时,region回收是基于复制算法,将待回收region中存活的对象放到Other Region中,然后清理掉本Region中的所有垃圾对象,从而不断清空新的Region在回收过程中。一旦free Region数量达到堆内存的5%,混合回收就会立即停止,也就是说本次混合回收结束。
2.5.6 G1采集
器使用场景
超过 1.50% 的堆被存活的对象占用;
2、对象分配和提升的速度差异很大;
3、垃圾回收时间极长,超过1秒;
堆内存大于4.8GB(推荐值);
5、停顿时间在500ms以内;
2.7 ZGC采集
器 2.7.1 ZGC简介
ZGC是JDK11中新加入的实验性低延迟垃圾采集
器。ZGC可以说是源自Azul System开发的C4(Concurrent Continuously Compacting Collector)采集
器。
2.7.2 ZGC目标
1.支持TB级堆;
2、最大GC停顿时间不超过10ms;
3.为以后的GC特性打下基础;
4、最坏情况下,吞吐量会降低15%;
无分代(临时):我们都知道上一代垃圾采集
器是基于“大多数对象有生有死”的假设。事实上,大多数系统的对象分配行为确实符合这个假设。那为什么ZGC不分代呢?由于分代实现比较麻烦,笔者先实现了一个比较简单好用的单代版本,后期会优化。
2.7.3 ZGC内存布局
ZGC采集
器基于Region内存布局,暂不设置分代,使用read barriers、color pointers等技术实现并发mark-sort算法,是一款以低延迟为首要目标的垃圾采集
器. 设备。
ZGC的Region可以有三种容量,如图3-19所示:大、中、小:
Small Region(小区域):容量固定为2MB,用于放置小于256KB的小对象。
Medium Region(中型区域):容量固定为32MB,用于放置大于等于256KB但小于4MB的对象。
Large Region(大区域):容量不固定,可以动态变化,但必须是2MB的整数倍,用于放置4MB以上的大对象。每个large region只会存储一个large object,这也说明虽然名字叫“large region”,但实际容量可能比medium region要小,最小容量可以低至4MB。Large Region在ZGC的实现中不会进行重分布(重分布是ZGC的一个处理动作,用于采集
器阶段的对象复制,后面会介绍),因为复制大对象的成本非常高。
2.7.4 彩色指针
前一个垃圾采集
器的GC信息保存在对象头中,而ZGC垃圾采集
器的GC信息保存在指针中。
" />
每个对象都有一个64位的指针,这64位分为:
18 位:保留以备后用;
1位:Finalizable标志位,这个位与并发引用处理有关,表示这个对象只能通过finalizer访问;
1位:Remapped flag,设置该位的值后,对象不指向relocation set(relocation set表示需要GC
区域集合);
1位:Marked1标识;
1位:Marked0标识,上面的Marked1都是辅助GC的标记对象;
42位:对象的地址(所以可以支持2^42=4T内存):
为什么有2个标记标记?
在每个 GC 周期的开始,使用过的标记位被交换,使在前一个 GC 周期中更正的标记状态无效,并且所有引用都变为未标记。
GC循环1:使用mark0,循环结束时所有引用标记都会变成01。
GC循环2:使用mark1,那么期望的mark标记为10,所有的引用都可以remark。
通过配置ZGC后对对象指针的分析,可以看出对象指针必须是64位的,那么ZGC就不能支持32位的操作系统,同样不能支持压缩指针(CompressedOops,压缩指针也是32位的)
彩色指针的三大优势:
1、一旦一个Region的存活对象被remove,这个Region就可以立即被释放并重新使用,而不需要等到整个堆中对该Region的所有引用都被纠正之后才能被清理掉。如果有空闲的Region,ZGC可以完成采集
。
2. 颜色指针可以大大减少垃圾回收时使用的内存屏障的数量。ZGC 只使用读屏障。
3、颜色指针可扩展性强。它可以作为一个可扩展的存储结构来记录更多与对象标记和重定位过程相关的数据,以便在未来进一步提高性能。
2.7.5 读屏障
在并发重分配的过程中,ZGC通过复制算法将原来的region(里面的对象)移动到另一个region,需要更新新的地址。什么时候更新?这个过程是一个懒惰的过程,它不会一直更新。当我从堆中获取旧对象值时,我得到了一个引用。此引用的颜色指针在进行并发标记时更改了颜色。在旧的引用过程中,颜色指针部分也被接管了。取的时候会加一个read barrier,判断指针是否被修改。如果它已被修改,它将在内部将旧引用移动到已使用的更新地址。转发表记录了旧对象到新对象的转发关系。
2.7.6 ZGC问题及解决方案
ZGC最大的问题是漂浮垃圾。ZGC的停顿时间在10ms以下,但是ZGC的执行时间还是比这个时间长了很多。如果ZGC的整个过程需要执行10分钟,这期间由于对象分配率高,会产生大量的新对象。这些对象很难进入本次GC,只能在下一次GC中回收。这些对象只能等到下一次GC才能回收的对象是浮动垃圾。
解决方案
目前唯一的办法就是增加堆的容量,让程序获得更多的喘息时间,但这也是治标不治本。如果要从根本上解决这个问题,还是需要引入分代采集
,让新的对象都在一个专门的区域创建。
2.7.7 ZGC参数设置
启用ZGC比较简单,设置JVM参数即可:-XX:+UnlockExperimentalVMOptions "-XX:+UseZGC"。调优并不难,因为ZGC的调优参数不多,远没有CMS复杂。和G1一样,可以调优的参数比较少,大部分工作都可以由JVM自动完成。ZGC可以调优的参数如下图:
3. 垃圾回收底层算法实现 3.1 三色标记
在gc过程或者并发标记过程中,主要有三种颜色
黑色:表示对该对象的所有引用都已被垃圾采集
器扫描过,是一个活的(非垃圾)对象;
灰色:表示这个对象至少有一个引用没有被垃圾采集
器扫描过;
白色:表示这个对象还没有被垃圾采集
器访问过(没有对象被gc root引用,对象不可达);
3.2 多标签漂浮垃圾
因为应用线程没有停止(执行栈线程,没有STW机制,即栈线程中局部变量表、操作数栈、动态链接、方法出口的指针不指向堆内存),会有多标签(非垃圾对象被标记为垃圾对象),多标签(浮动垃圾)会在下次垃圾回收时被清除。
3.3 缺失标签——读写障碍
缺失标记:A->D,开头后面没有加法,B->D,开头后面有去掉。A不会被重新扫描,B重新扫描后也无法扫描D;D(以后会删除)会漏掉(阿里巴巴可能会问),D不是垃圾对象,会被标记为垃圾对象。造成程序严重错误。
增量更新:在并发标记的过程中,将赋值(新引用)保存在一个集合中,(重标记时是为了解决并发标记时多标记或漏标记的问题,找到新添加记录的引用并重新扫描);简单理解:新添加的引用源和被引用对象记录在一个集合中(底层是C++),源引用用黑色标注。(一旦黑色对象有了新插入的对白色对象的引用,它就变成了灰色对象)
原创
快照(Snapshot At The Beginning, SATB):(没看懂,再听一遍)在赋值前将旧的引用以快照的形式保存在集合中。在remarking的过程中,采集
中的所有引用都被标记为黑色,如果是黑色,则本轮不回收(会变成浮动垃圾,下轮回收)。
增量更新和原创
快照都是通过写屏障来记录的。
Write barrier:增加新的引用或减少引用是通过赋值来实现的。无论是增量更新还是原创
快照引用的记录,都必须采集
到集合中,无论是赋值前还是赋值后(代码中的操作屏障),通过write barrier记录到集合中
赋值前后做一些处理(有点像AOP),(write barrier)写前操作,(write barrier)写后操作(增量更新)。
CMS 实现写屏障+增量更新来处理丢失的标签。(写操作异步处理以提高性能)
为什么G1使用原创
快照,而CMS使用增量更新来处理丢失的标签?
因为CMS在增量更新时会继续扫描节点对象的引用,所以G1不会再深度扫描原创
快照中的对象,而是简单地标记它们,等待下一轮GC进行深度扫描。(G1有很多不同的region,CMS是老年代区域,如果再深度重新扫描,G1的成本会比CMS高)。
4.内存设置和卡表
Remember Set:为了解决新生代中对象的引用被老年代引用的问题,专门在新生代的内存中设置了这些老年代引用的对象(不会有很多交叉)生成引用),并且这些对象的集合使用内存集调用。
Cardtable:Cardtable和memory set就像Java语言中HashMap和Map的关系。内存集是用卡表实现的,内存区域(卡页)在老年代是一块块划分的,或者叫页内存卡(约512字节)。如果page memory(card)中有指向young的对象 如果在generation中有对象,则将这个card page标记为dirty。卡表(Cardtable)的底层实现是一个数组,数组中每个元素对应的内存区域称为页内存(card page)。卡表底层也是通过write barrier实现的。卡页在老年代,卡表在新生代(堆)。
5.如何选择垃圾采集
器
1、优先调整堆的大小,让服务器自己选择;
2.如果内存小于100M,使用串口采集
器;
3、如果是单核,没有停顿时间要求,serial或者JVM可以自己选择;
4.如果允许暂停时间超过1秒,选择parallel或者JVM来选择;
5、如果响应时间最重要,不能超过1秒,使用并发采集
器;
6、4G以下可以用parallel,4-8G可以用ParNew+CMS,8G以上可以用G1,几百G以上可以用ZGC;
6. 安全点和安全区
安全点:在做GC的过程中,不要想做就马上做GC。当用户线程在做GC的时候,会判断flag是什么(比如0和1,1表示到达安全点,0没有到达,所有用户线程都会轮询看这个flag)当用户线程flags全为1,就会被挂起,当所有用户线程flags都为1时(都到达安全点时),就会触发GC。
安全点的位置主要有以下几点:
1、方法返回前;
2.调用方法后;
3.抛出异常的位置;
4.循环结束;
安全区:是为正在执行的线程确定的。比如一个线程处于休眠状态,无法响应JVM的中断请求,进而运行到Safe Point。因此,JVM引入了Safe Region,也就是说在一个代码块中,引用关系不会发生变化,在这个区域的任何地方启动GC都是安全的。
总结
例如:常见的垃圾采集
算法mark-copy algorithm, mark-clear algorithm, mark-sort algorithm,常见的垃圾采集
器Serial, Parallel, ParNew, CMS, G1, ZGC,主要是三种垃圾:CMS, G1, ZGC 的运行collector的流程,并发标记过程中的missing mark问题,以及相应的解决方案。
解决方案:*敏*感*词*采集的图像可以用调用opencv处理吗
用
OpenCV打开USB*敏*感*词*时,两个软件采集到的图像结果分别是以上两张,我用OpenCV采集的第一张图片
第二张图是用别人的软件采集
的,感觉色差好大,在OpenCV中我觉得相机本身的很多属性都设置不定,原因是两个软件使用的库不同,而且拍到的图片默认参数不同,所以想用DirectShow, 据说有一些东西可以在里面设置属性。
安装直接显示
1. 下载、安装和配置 OpenCV
2.免费下载直接显示,哈哈(
)
3. 解压缩到 OpenCV 所在的文件夹。
4. VS2010 新 Win32 项目,空文档。
5. 配置 OpenCV(还有很多其他文章,这里就不提了)。
6. 配置直接显示:在属性管理器中,在 VC++ 目录收录
项的直接显示下添加收录
文件路径。
在库中的 DirectShow 下添加库路径。
7.将 CameraDS.h CameraDS.cpp 和目录 DirectShow 复制到您的项目中
此路径收录
所需的实验过程。它也可以下载到CameraDS.h CameraDS.cpp。
8.如有必要,在属性管理器的常规下,将使用Unicode字符集更改为unset,这样可以避免一些编译错误。//
/
使用 DirectShow 拍摄视频
作者:于世琪 ()
//由于:
HardyAI@OpenCV 中国/
/flymanbox@OpenCV 中国(表彰他对功能相机名称和帧宽/高设置的贡献)
最后修改日期:2009 年 4 月 9 日
//
使用说明:
将 CameraDS.h CameraDS.cpp 和目录 DirectShow 复制到您的项目中
项目->设置->设置:(所有配置)->C/C++->类别(预处理器)->其他收录
目录
设置为直接显示/收录
项目->设置->设置:(所有配置)->链接->类别(输入)->其他库目录
设置为 DirectShow/Lib//
/
#include
#include “相机DS.h”
#include
常量字符 *g_szTitle = “相机”;
int main()
" />
{
1. 考虑到已经有
一个窗口显示图像,无需再次驾驶相机,即使你开车下来,相机也已经被占用了。
if(IsWindowVisible(FindWindow(NULL, g_szTitle)))
{
返回 (-1);
}
只需获取摄像机数量
int m_iCamCount = CCameraDS::CameraCount();
printf(“There are 有 %d camera.\n”, m_iCamCount);
if(m_iCamCount == 0)
{
返回 (-1);
}
CCameraDS m_CamDS;
获取所有摄像机的名称
for(int i = 0; i < m_iCamCount; i++)
{
字符 szCamName[1024];
int retval = m_CamDS.CameraName(i, szCamName, sizeof(szCamName));
if(retval >0)
{
printf(“Camera #%d's Name is '%s'.\n”, i, szCamName);
}
还
{
printf(“无法获取相机 #%d 的名称。\n”, i);
}
}
2.考虑到如果有多个*敏*感*词*,或者其中一个或几个被其他程序占用,则需要逐个遍历它们
直到找到一个可用。
int m_iCamNum = 0;相机编号
IplImage *pFrame = NULL;
而(m_iCamNum < m_iCamCount)
{
" />
if((! m_CamDS.OpenCamera(m_iCamNum, true , 320, 240)) ||((pFrame = m_CamDS.QueryFrame()) == NULL))
{
m_iCamNum++;
}
还
{ // 找到合适的相机并退出循环。
破;
}
关闭
相机,您必须将其关闭,因为下一次测试即将进行,并且在检测之前必须清除当前足迹。
m_CamDS.关闭相机();
}
if(m_iCamNum == m_iCamCount)
{
fprintf(stderr, “无法打开相机或被其他应用程序使用。\n”);
返回 (-1);
}
cvNamedWindow(g_szTitle);
显示
cvShowImage(g_szTitle, pFrame);
而(1)
{
获取框架
pFrame = m_CamDS.QueryFrame();
显示
cvShowImage(g_szTitle, pFrame);
if (cvWaitKey(20) == 'q')
{
破;
}
}
m_CamDS.关闭相机();你可以不调用这个函数,CCameraDS会在相机被破坏时自动关闭相机
cvDestroyWindow(g_szTitle);
返回 0;
}