jvm回收机制的三大算法(三色标记法与垃圾回收器(CMS、G1))

2024-07-17 20:18:01 27

jvm回收机制的三大算法(三色标记法与垃圾回收器(CMS、G1))

本文目录

三色标记法与垃圾回收器(CMS、G1)

JVM中的CMS、G1垃圾回收器所使用垃圾回收算法即为三色标记法。

三色标记法将对象的颜色分为了黑、灰、白,三种颜色。

存在问题:

浮动垃圾:并发标记的过程中,若一个已经被标记成黑色或者灰色的对象,突然变成了垃圾,此时,此对象不是白色的不会被清除,重新标记也不能从GC Root中去找到,所以成为了浮动垃圾,这种情况对系统的影响不大,留给下一次GC进行处理即可。 对象漏标问题(需要的对象被回收):并发标记的过程中,一个业务线程将一个未被扫描过的白色对象断开引用成为垃圾(删除引用),同时黑色对象引用了该对象(增加引用)(这两部可以不分先后顺序);因为黑色对象的含义为其属性都已经被标记过了,重新标记也不会从黑色对象中去找,导致该对象被程序所需要,却又要被GC回收,此问题会导致系统出现问题,而CMS与G1,两种回收器在使用三色标记法时,都采取了一些措施来应对这些问题,CMS对增加引用环节进行处理(Increment Update),G1则对删除引用环节进行处理(SATB)。

在JVM虚拟机中有两种常见垃圾回收器使用了该算法:

CMS(Concurrent Mark Sweep)

CMS,是非常有名的JVM垃圾回收器,它起到了承上启下的作用,开启了并发回收的篇章。 但是CMS由于许多小问题,现在基本已经被淘汰。

增量更新(Increment Update) 在应对漏标问题时,CMS使用了Increment Update方法来做: 在一个未被标记的对象(白色对象)被重新引用后,==引用它的对象==,若为黑色则要变成灰色,在下次二次标记时让GC线程继续标记它的属性对象。 但是就算时这样,其仍然是存在漏标的问题:

在一个灰色对象正在被一个GC线程回收时,当它已经被标记过的属性指向了一个白色对象(垃圾) 而这个对象的属性对象本身还未全部标记结束,则为灰色不变 而这个GC线程在标记完最后一个属性后,认为已经将所有的属性标记结束了,将这个灰色对象标记为黑色,被重新引用的白色对象,无法被标记

补充,CMS除了这个缺陷外,仍然存在两个个较为致命的缺陷:

解决方案:使用Mark-Sweep-Compact算法,减少垃圾碎片

当JVM认为内存不够了,再使用CMS进行并发清理内存可能会发生OOM的问题,而不得不进行Serial Old GC,Serial Old是单线程垃圾回收,效率低

解决方案:降低触发CMS GC的阈值,让浮动垃圾不那么容易占满老年代

G1(Garbage First)

从G1垃圾回收器开始,G1的物理内存不再分代,而是由一块一块的Region组成;逻辑分代仍然存在。

前置知识 — Card Table(多种垃圾回收器均具备)

由于在进行YoungGC时,我们在进行对一个对象是否被引用的过程,需要扫描整个Old区,所以JVM设计了CardTable,将Old区分为一个一个Card,一个Card有多个对象;如果一个Card中的对象有引用指向Young区,则将其标记为Dirty Card,下次需要进行YoungGC时,只需要去扫描Dirty Card即可。

Card Table 在底层数据结构以 Bit Map实现。

CSet(Collection Set)

SATB(Snapshot At The Beginning) 在应对漏标问题时,CMS使用了SATB方法来做:

因为SATB在重新标记环节只需要去重新扫描那些被推到堆栈中的引用,并配合Rset来判断当前对象是否被引用来进行回收;

并且在最后G1并不会选择回收所有垃圾对象,而是根据Region的垃圾多少来判断与预估回收价值(指回收的垃圾与回收的STW时间的一个预估值),将一个或者多个Region放到CSet中,最后将这些Region中的存活对象压缩并复制到新的Region中,清空原来的Region。

问题:G1会不会进行Full GC? 会,当内存满了的时候就会进行Full GC;且JDK10之前的Full GC,为单线程的,所以使用G1需要避免Full GC的产生。 解决方案:

加大内存; 提高CPU性能,加快GC回收速度,而对象增加速度赶不上回收速度,则Full GC可以避免; 降低进行Mixed GC触发的阈值,让Mixed GC提早发生(默认45%)

G1的第一篇paper(附录1)发表于2004年,在2012年才在jdk1.7u4中可用。oracle官方计划在jdk9中将G1变成默认的垃圾收集器,以替代CMS。为何oracle要极力推荐G1呢,G1有哪些优点?

首先,G1的设计原则就是简单可行的性能调优

开发人员仅仅需要声明以下参数即可:

其中-XX:+UseG1GC为开启G1垃圾收集器,-Xmx32g 设计堆内存的最大内存为32G,-XX:MaxGCPauseMillis=200设置GC的最大暂停时间为200ms。如果我们需要调优,在内存大小一定的情况下,我们只需要修改最大暂停时间即可。

其次,G1将新生代,老年代的物理空间划分取消了。

这样我们再也不用单独的空间对每个代进行设置了,不用担心每个代内存是否足够。

取而代之的是,G1算法将堆划分为若干个区域(Region),它仍然属于分代收集器。不过,这些区域的一部分包含新生代,新生代的垃圾收集依然采用暂停所有应用线程的方式,将存活对象拷贝到老年代或者Survivor空间。老年代也分成很多区域,G1收集器通过将对象从一个区域复制到另外一个区域,完成了清理工作。这就意味着,在正常的处理过程中,G1完成了堆的压缩(至少是部分堆的压缩),这样也就不会有cms内存碎片问题的存在了。

在G1中,还有一种特殊的区域,叫Humongous区域。 如果一个对象占用的空间超过了分区容量50%以上,G1收集器就认为这是一个巨型对象。这些巨型对象,默认直接会被分配在年老代,但是如果它是一个短期存在的巨型对象,就会对垃圾收集器造成负面影响。为了解决这个问题,G1划分了一个Humongous区,它用来专门存放巨型对象。如果一个H区装不下一个巨型对象,那么G1会寻找连续的H分区来存储。为了能找到连续的H区,有时候不得不启动Full GC。

PS:在java 8中,持久代也移动到了普通的堆内存空间中,改为元空间。

对象分配策略

说起大对象的分配,我们不得不谈谈对象的分配策略。它分为3个阶段:

对TLAB空间中无法分配的对象,JVM会尝试在Eden空间中进行分配。如果Eden空间无法容纳该对象,就只能在老年代中进行分配空间。

最后,G1提供了两种GC模式,Young GC和Mixed GC,两种都是Stop The World(STW)的。下面我们将分别介绍一下这2种模式。

Young GC主要是对Eden区进行GC,它在Eden空间耗尽时会被触发。在这种情况下,Eden空间的数据移动到Survivor空间中,如果Survivor空间不够,Eden空间的部分数据会直接晋升到年老代空间。Survivor区的数据移动到新的Survivor区中,也有部分数据晋升到老年代空间中。最终Eden空间的数据为空,GC停止工作,应用线程继续执行。

这时,我们需要考虑一个问题,如果仅仅GC 新生代对象,我们如何找到所有的根对象呢? 老年代的所有对象都是根么?那这样扫描下来会耗费大量的时间。于是,G1引进了RSet的概念。它的全称是Remembered Set,作用是跟踪指向某个heap区内的对象引用。

在CMS中,也有RSet的概念,在老年代中有一块区域用来记录指向新生代的引用。这是一种point-out,在进行Young GC时,扫描根时,仅仅需要扫描这一块区域,而不需要扫描整个老年代。

但在G1中,并没有使用point-out,这是由于一个分区太小,分区数量太多,如果是用point-out的话,会造成大量的扫描浪费,有些根本不需要GC的分区引用也扫描了。于是G1中使用point-in来解决。point-in的意思是哪些分区引用了当前分区中的对象。这样,仅仅将这些对象当做根来扫描就避免了无效的扫描。由于新生代有多个,那么我们需要在新生代之间记录引用吗?这是不必要的,原因在于每次GC时,所有新生代都会被扫描,所以只需要记录老年代到新生代之间的引用即可。

需要注意的是,如果引用的对象很多,赋值器需要对每个引用做处理,赋值器开销会很大,为了解决赋值器开销这个问题,在G1 中又引入了另外一个概念,卡表(Card Table)。一个Card Table将一个分区在逻辑上划分为固定大小的连续区域,每个区域称之为卡。卡通常较小,介于128到512字节之间。Card Table通常为字节数组,由Card的索引(即数组下标)来标识每个分区的空间地址。默认情况下,每个卡都未被引用。当一个地址空间被引用时,这个地址空间对应的数组索引的值被标记为”0″,即标记为脏被引用,此外RSet也将这个数组下标记录下来。一般情况下,这个RSet其实是一个Hash Table,Key是别的Region的起始地址,Value是一个集合,里面的元素是Card Table的Index。

Young GC 阶段:

Mix GC不仅进行正常的新生代垃圾收集,同时也回收部分后台扫描线程标记的老年代分区。

它的GC步骤分2步:

全局并发标记(global concurrent marking) 拷贝存活对象(evacuation) 在进行Mix GC之前,会先进行global concurrent marking(全局并发标记)。 global concurrent marking的执行过程是怎样的呢?

在G1 GC中,它主要是为Mixed GC提供标记服务的,并不是一次GC过程的一个必须环节。global concurrent marking的执行过程分为五个步骤:

初始标记(initial mark,STW) 在此阶段,G1 GC 对根进行标记。该阶段与常规的 (STW) 年轻代垃圾回收密切相关。 根区域扫描(root region scan) G1 GC 在初始标记的存活区扫描对老年代的引用,并标记被引用的对象。该阶段与应用程序(非 STW)同时运行,并且只有完成该阶段后,才能开始下一次 STW 年轻代垃圾回收。 并发标记(Concurrent Marking) G1 GC 在整个堆中查找可访问的(存活的)对象。该阶段与应用程序同时运行,可以被 STW 年轻代垃圾回收中断 最终标记(Remark,STW) 该阶段是 STW 回收,帮助完成标记周期。G1 GC 清空 SATB 缓冲区,跟踪未被访问的存活对象,并执行引用处理。 清除垃圾(Cleanup,STW) 在这个最后阶段,G1 GC 执行统计和 RSet 净化的 STW 操作。在统计期间,G1 GC 会识别完全空闲的区域和可供进行混合垃圾回收的区域。清理阶段在将空白区域重置并返回到空闲列表时为部分并发。

提到并发标记,我们不得不了解并发标记的三色标记算法。它是描述追踪式回收器的一种有用的方法,利用它可以推演回收器的正确性。 首先,我们将对象分成三种类型的。

根对象被置为黑色,子对象被置为灰色。

继续由灰色遍历,将已扫描了子对象的对象置为黑色。

遍历了所有可达的对象后,所有可达的对象都变成了黑色。不可达的对象即为白色,需要被清理。

这看起来很美好,但是如果在标记过程中,应用程序也在运行,那么对象的指针就有可能改变。这样的话,我们就会遇到一个问题:对象丢失问题

我们看下面一种情况,当垃圾收集器扫描到下面情况时:

这时候应用程序执行了以下操作:

这样,对象的状态图变成如下情形:

这时候垃圾收集器再标记扫描的时候就会下图成这样:

很显然,此时C是白色,被认为是垃圾需要清理掉,显然这是不合理的。那么我们如何保证应用程序在运行的时候,GC标记的对象不丢失呢?有如下2中可行的方式:

在插入的时候记录对象 在删除的时候记录对象 刚好这对应CMS和G1的2种不同实现方式:

在CMS采用的是增量更新(Incremental update),只要在写屏障(write barrier)里发现要有一个白对象的引用被赋值到一个黑对象 的字段里,那就把这个白对象变成灰色的。即插入的时候记录下来。

在G1中,使用的是STAB(snapshot-at-the-beginning)的方式,删除的时候记录所有的对象,它有3个步骤:

这样,G1到现在可以知道哪些老的分区可回收垃圾最多。 当全局并发标记完成后,在某个时刻,就开始了Mix GC。这些垃圾回收被称作“混合式”是因为他们不仅仅进行正常的新生代垃圾收集,同时也回收部分后台扫描线程标记的分区。混合式垃圾收集如下图:

混合式GC也是采用的复制的清理策略,当GC完成后,会重新释放空间。

至此,混合式GC告一段落了。下一小节我们讲进入调优实践。

MaxGCPauseMillis调优

前面介绍过使用GC的最基本的参数:

前面2个参数都好理解,后面这个MaxGCPauseMillis参数该怎么配置呢?这个参数从字面的意思上看,就是允许的GC最大的暂停时间。G1尽量确保每次GC暂停的时间都在设置的MaxGCPauseMillis范围内。 那G1是如何做到最大暂停时间的呢?这涉及到另一个概念,CSet(collection set)。它的意思是在一次垃圾收集器中被收集的区域集合。

Young GC:选定所有新生代里的region。通过控制新生代的region个数来控制young GC的开销。 Mixed GC:选定所有新生代里的region,外加根据global concurrent marking统计得出收集收益高的若干老年代region。在用户指定的开销目标范围内尽可能选择收益高的老年代region。 在理解了这些后,我们再设置最大暂停时间就好办了。 首先,我们能容忍的最大暂停时间是有一个限度的,我们需要在这个限度范围内设置。但是应该设置的值是多少呢?我们需要在吞吐量跟MaxGCPauseMillis之间做一个平衡。如果MaxGCPauseMillis设置的过小,那么GC就会频繁,吞吐量就会下降。如果MaxGCPauseMillis设置的过大,应用程序暂停时间就会变长。G1的默认暂停时间是200毫秒,我们可以从这里入手,调整合适的时间。

其他调优参数

避免使用以下参数:

避免使用 -Xmn 选项或 -XX:NewRatio 等其他相关选项显式设置年轻代大小。固定年轻代的大小会覆盖暂停时间目标。

触发Full GC

在某些情况下,G1触发了Full GC,这时G1会退化使用Serial收集器来完成垃圾的清理工作,它仅仅使用单线程来完成GC工作,GC暂停时间将达到秒级别的。整个应用处于假死状态,不能处理任何请求,我们的程序当然不希望看到这些。那么发生Full GC的情况有哪些呢?

并发模式失败 G1启动标记周期,但在Mix GC之前,老年代就被填满,这时候G1会放弃标记周期。这种情形下,需要增加堆大小,或者调整周期(例如增加线程数-XX:ConcGCThreads等)。

晋升失败或者疏散失败 G1在进行GC的时候没有足够的内存供存活对象或晋升对象使用,由此触发了Full GC。可以在日志中看到(to-space exhausted)或者(to-space overflow)。解决这种问题的方式是:

巨型对象分配失败 当巨型对象找不到合适的空间进行分配时,就会启动Full GC,来释放空间。这种情况下,应该避免分配大量的巨型对象,增加内存或者增大-XX:G1HeapRegionSize,使巨型对象不再是巨型对象。

由于篇幅有限,G1还有很多调优实践,在此就不一一列出了,大家在平常的实践中可以慢慢探索。最后,期待java 9能正式发布,默认使用G1为垃圾收集器的java性能会不会又提高呢?

G1处理和传统的垃圾收集策略是不同的,关键的因素是它将所有的内存进行了子区域的划分。

总结

G1是一款非常优秀的垃圾收集器,不仅适合堆内存大的应用,同时也简化了调优的工作。通过主要的参数初始和最大堆空间、以及最大容忍的GC暂停目标,就能得到不错的性能;同时,我们也看到G1对内存空间的浪费较高,但通过**首先收集尽可能多的垃圾(Garbage First)的设计原则,可以及时发现过期对象,从而让内存占用处于合理的水平。

参考链接:***隐藏网址******隐藏网址***

jvm中垃圾回收机制有哪些

Java虚拟机(JVM)的堆中储存着正在运行的应用程序所建立的所有对象,这些对象一般来说,堆的是由垃圾回收 来负责的,尽管JVM规范并不要求特殊的垃圾回收

jvm垃圾回收是什么时候触发的垃圾回收算法

1.垃圾回收目的:Java语言中一个显著的特点就是引入了垃圾回收机制,使c++程序员最头疼的内存管理的问题迎刃而解,它使得Java程序员在编写程序的时候不再需要考虑内存管理。由于有个垃圾回收机制,Java中的对象不再有“作用域”的概念,只有对象的引用才有“作用域”。垃圾回收可以有效的防止内存泄露,有效的使用空闲的内存。ps:内存泄露是指该内存空间使用完毕之后未回收,在不涉及复杂数据结构的一般情况下,Java 的内存泄露表现为一个内存对象的生命周期超出了程序需要它的时间长度,我们有时也将其称为“对象游离”。2.由于对象进行了分代处理,因此垃圾回收区域、时间也不一样。GC有两种类型:Scavenge GC和Full GC。Scavenge GC一般情况下,当新对象生成,并且在Eden申请空间失败时,就会触发Scavenge GC,对Eden区域进行GC,清除非存活对象,并且把尚且存活的对象移动到Survivor区。然后整理Survivor的两个区。这种方式的GC是对年轻代的Eden区进行,不会影响到年老代。因为大部分对象都是从Eden区开始的,同时Eden区不会分配的很大,所以Eden区的GC会频繁进行。因而,一般在这里需要使用速度快、效率高的算法,使Eden去能尽快空闲出来。Full GC对整个堆进行整理,包括Young、Tenured和Perm。Full GC因为需要对整个堆进行回收,所以比Scavenge GC要慢,因此应该尽可能减少Full GC的次数。在对JVM调优的过程中,很大一部分工作就是对于FullGC的调节。有如下原因可能导致Full GC:1.年老代(Tenured)被写满2.持久代(Perm)被写满3.System.gc()被显示调用4.上一次GC之后Heap的各域分配策略动态变化

JVM有哪些垃圾回收算法

在JVM运行时数据区存在一个堆区, 堆是一个巨大的对象池。在这个对象池中管理着数量巨大的对象实例,而池中对象的引用层次,有的是很深的。一个被频繁调用的接口,每秒生成对象的速度,是很大的,同时,对象之间的关系,形成了一张巨大的网。Java 一直在营造一种无限内存的氛围,但对象不能只增不减,所以需要垃圾回收;那 JVM 是如何判断哪些对象应该被回收?哪些应该被保持呢?这就要用到JVM的垃圾回收机制了,也就是我们常说的GC(Garbage Collection),也叫垃圾收集器。

5、垃圾回收机制

JVM的垃圾回收机制主要涉及三个方面的问题: 1.JVM有哪些垃圾回收算法?各自有什么优势? 2.CMS垃圾回收器是如何工作的?有哪些阶段? 3.服务卡顿的元凶到底是什么? Java不用程序来管理内存的回收,但这些内存是如何回收的? 其实,JVM有专门的线程在做这件事情。当内容空间达到一定条件时,会自动触发,这个过程就叫GC,负责GC的组件被称为垃圾回收器。JVM规范没有规定垃圾回收器怎么实现,它只需要保证不要把正在使用的对象回收掉就可以。在现在的服务器环境中,经常被使用的垃圾回收器有CMS和G1,但JVM还有其它几个常见的垃圾回收器。 GC的过程是先找到活跃的对象,然后把其他不活跃的对象判定为垃圾,然后删除,所以GC只与活跃的对象有关,和堆的大小无关。 接下来学习下分代垃圾回收的内存划分和GC过程,再有就是常见的垃圾回收器。 这篇比较重要,因为几乎所有的垃圾回收器都是在这些基本思想上演化出来的。 GC的第一步就是找出活跃的对象,根据GC Roots遍历所有的可达对象,这个过程就叫作标记。 如上图所示,圆圈代表对象,绿色的代表GC Roots,红色的代表可以追溯到的对象,标记后,有多个灰色的圆圈,代表都是可被回收的对象。 清除阶段就是把未被标记的对象回收掉。 这种方式有一个明显的问题,会产生碎片空间。 比如申请了1k、2k、3k、4k、5k的内存 由于某些原因,2k和4k的内存不再使用,交给垃圾回收器回收。 解决碎片问题,就需要进行内存整理。 有一个思路就是提送一个对等的内存空间,将存活的对象复制过去,然后清除员内存空间。 在程序设计时,一般遇到扩缩容或者碎片整理问题时,复制算法都是非常有效的。比如:HashMap的扩容使用的是同样的思路,Redis的rehash也是如此。 整个过程如下图 这种方式看似完美,解决了碎片问题,但是弊端也非常明显,它浪费了一半的内存空间来做这个事情,如果原本资源就有限,这就是一种无法容忍的浪费。 不用分配一个对等的空间也是可以完成内存的整理工作。 可以把内存想象成一个非常大的数组,根据随机的index删除了一些数据,那么对数组的清理不需要另外一个数组来进行支持的,使用程序就可以。 主要思路是移动所有的存活对象,且按照内存地址顺序依次排列,然后将末端内存地址以后的内存全部收回。 对象的引用关系一般是非常复杂的,从效率上来说,一般整理算法是要低于复制算法的。 JVM的垃圾回收器,都是对以上几种朴素算法的结合使用,简单看一下它们的特点: 效率一般,缺点是回造成内存碎片的问题。 复制算法是所有算法里面效率最高的,缺点是造成一定的空间浪费。 效率比前两者要差,但没有空间浪费,也消除了内存碎片问题。 所以没有最优的算法,只有最合适的算法。 JVM是计算节点,而不是存储节点。最理想的情况就是对象使用完成之后,它的生命周期立马就结束了,而那些被频繁访问的资源,我们希望它能够常驻在内存里。 对象大致可以分为两类: 1.大部分对象的生命周期都很短 2.其他对象则很可能会存活很长时间 现在的垃圾回收器都会在物理上或者逻辑上,把这两类对象进行分区。我们把死的快的对象所占的区域叫年轻代(Young Generation)。把其他活的长的对象所占的区域叫作老年代(Old Generation),老年代在有时候会叫作Tenured Generation。 年轻代使用的垃圾回收算法是复制算法,因为年轻代发生GC后,会有非常少的对象存活,复制这部分对象是非常高效的 年轻代的内部分区 如图所示,年轻代分为:一个伊甸园空间(Eden),两个幸存者空间(Survivor)。 当年轻代中的Eden区分配满的时候,就会触发年轻代的GC(Minor GC),具体过程如下 1.在Eden区执行了第一次GC之后,存活的对象会被移动到其中一个Suvivor分区(from); 2.Eden区再次GC,这是会采用复制算法,将Eden和from区一起清理,存活的对象会被复制到to区;接下来只需要清空from区就可以了 在整个过程中总会有一个Survivor分区是空置的。Eden、from、to的默认比例是8:1:1,所以只会造成10%的空间浪费。 这个比例是由参数-XX:SurvivorRatio进行配置的(默认为8)。 补充下不常提到的TLAB。TLAB全称是Thread Local Allocation Buffer,JVM默认给每个线程开辟一个buffer区域,用来加速对象分配。这个buffer就放在Eden区中。 这个道理和Java语言中的ThreadLocal类似,避免了对公共区的操作,以及一些锁竞争。 老年代一般使用"标记-清除"、"标记-整理"算法。因为老年代的对象存活率一般是比较高的,空间又比较大,拷贝起来并不划算,不如采取就地收集的方式。 对象进入老年代的途径分类 如果对象够老,会通过"提升"进入老年代。关于对象老不老,是通过它的年龄来判断的。每发生一次Minor GC,存活下来的对象年龄都会加1,直到达到一定的阀值,就会提升到老年代, 这些对象如果变的不可达,直到老年代发生GC的时候才会被清理掉。 这个阀值可以通过参数 -XX:+MaxTenuringThreshold进行配置,最大值是15,因为它是用4bit存储的(所以把这个值调的很大的文章,是没有什么根据的)。 每次存活的对象,都会放入其中一个幸存区,这个区域默认比例是10%,但无法保证每次存活的对象都小于10%,当Survivor空间不够,就需要依赖其它内存(老年代)进行分配担保。这个时候,对象也会直接在老年代上分配。 超出某个大小的对象直接在老年代分配,通过参数设置-XX:PretenureSizeThreshold进行配置的,默认为0,默认全部在Eden区进行分配。 有的垃圾回收算法,并不要求age必须达到15才能晋升到老年代,它会使用一些动态的计算方法。比如,如果幸存区中相同年龄对象大小的和,大于幸存区的一半,大于或者等于age的对象将会直接进入老年代。 这些动态判定一半不受外部控制 对象的引用关系时一个巨大的网状,有的对象在Eden区,有的可能在老年代,那么这种跨代的引用是如何处理的呢?由于Minor GC是单独发生的,如果一个老年代的对象引用了它,如何确保能够让年轻代的对象存活呢? 对于是、否的判断,我们通常都会用到Bitmap(位图)和布隆过滤器来加快搜索的速度,需要另外再学习下(如果不知道这两个概念的话) JVM也是用了类似的方法。其实,老年代是被分成众多的卡页(Card Page)的(一般数量是2的次幂) 卡表(Card Table)就是用于标记卡页状态的一个集合,每个卡表对应一个卡页。 如果年轻代有对象分配,而且老年代有对象指向这个新对象,那么这个老年代对象所对应内存的卡页就会被标识为dirty,卡表只需要非常小的存储空间就可以保留这些状态,垃圾回收时,就可以先读这个卡表,进行快速的判断。 接下来学习HotSpot的几个垃圾回收器,每种回收器都有各自的特点。在平常的GC优化时,一定要清楚现在用的是那种垃圾回收器。 下图包含了年轻代和老年代的划分,方便接下来的学习参考 处理GC的只有一条线程,并且在垃圾回收的过程中暂停一切用户线程。 这是最简单的垃圾回收器,虽然简单,但十分高效,通常用在客户端应用上。因为客户端应用不会频繁创建很多对象,用户也不会感觉出明显的卡顿。相反,它使用的资源更少,也更轻量级。 ParNew是Serial的多线程版本,由多条GC线程并行地进行垃圾清理。清理过程依然要停止用户线程。追求低停顿时间,与Serial唯一区别就是使用了多线程进行垃圾回收,在多CPU环境下性能比Serial会有一定程度的提升;但线程切换需要额外的开销,因此在单CPU环境中表现不如Serial。 另一个多线程版本的垃圾回收器。但与ParNew是有区别的 1.Parallel Scavenge:追求CPU吞吐量,能够在较短时间内完成指定任务,适合没有交互的后台计算,弱交互强计算。 2.ParNew:追求降低用户停顿时间,适合交互式应用,强交互弱计算。 与年轻代的Serial垃圾回收器对应,都是单线程版本,同样适合客户端使用。 年轻代Serial,使用复制算法。 老年代的Old Serial,使用标记-整理算法。 Parallel Old回收器是Parallel Scavenge 的老年代版本,追求CPU吞吐量。 CMS(Concurrent Mark Sweep)回收器是以获取最短GC停顿时间为目标的收集器,它在垃圾回收时使得用户线程和GC线程能够并发执行,因此在垃圾回收过程中用户也不会感到明显的卡顿。 长期看来,CMS垃圾回收器,是要被G1等垃圾回收器替换掉的,在Java8之后,使用它将会抛出一个警告! 除了上面几个垃圾回收器,我们还有G1、ZGC等更加高级的垃圾回收器,它们都有专门的配置参数来使其生效。 通过-XX:PrintCommandLineFlags参数,可以查看当前Java版本默认使用的垃圾回收器。在Java13中,默认的回收器就是G1。 以下是一些配置参数: 1.-XX:+UseSerialGC 年轻代和年老代回收器 2.-XX:+UseParNewGC 年轻代使用ParNew,老年代使用Serial Old。 3.-XX:+UseParallelOldGC 年轻代和老年代哦都市用并行回收器。 4.-XX:+UseConcMarkSweepGC 表示年轻代使用ParNew,老年代使用CMS。 5.-XX:+UseG1GC 使用G1垃圾回收器 6.-XX:+UseZGC 使用ZGC垃圾回收器 这些垃圾回收器的关系还是比较复杂的,请看下图 目前Java8还是主流使用版本,从Java8升级到高版本的Java体系是有一定成本的,所以CMS垃圾回收器还会持续一段时间 抛个问题,如果在垃圾回收的时候,又有新的对象进入怎么办? 为了保住程序不乱套,最好的办法就是暂停用户的一切线程,也就是在这段时间,是不能new对象的,只能等待,表象是在JVM上就是短暂的卡顿,什么都干不了,这个现象叫作Stop The World。 标记阶段,大多数是要STW的。如果不暂停用户进程,在标记对象的时候,有可能有其它用户线程会产生一些新的对象和引用,造成混乱。 现在的垃圾回收器,都会尽量去减少这个过程。但即使最先进的ZGC回收器,也会有短暂的STW过程。我们要做的就是在现有基础设施上,尽量减少GC停顿。 举例说明下 某个高并发服务的峰值流量是10万次/秒,后面有10台负载均衡的机器,那么每台机器平均下来需要1w/s。假如某台机器在这段时间内发生了STW,持续了一秒,那么至少需要10ms就可以返回的1万个请求,需要至少等待1秒。 在用户那里的表现就是系统发生了卡顿。如果我们的GC非常的频繁。这种卡顿就会特别的明显,严重影响用户体验。 虽然说Java为我们提供了非常棒的自动内存管理机制,但也不能滥用,因为它是有STW硬伤的。 介绍了堆的具体分区,年轻代和老年代。介绍了多个常用的垃圾回收器,不同的垃圾回收器有不同的特点。各种垃圾回收器都是为了解决头疼的STW问题,让GC时间更短,停顿更短,吞吐量更大。 接触了很多名词,总结如下 1.Mark 2.Sweep 3.Copy 4.Compact 1.Young generation 2.Survivor 3.Eden 4.Old Generation |Tenured Generation 5.GC --1.Minor GC --2.Major GC 1.weak generational hypothesis 2.分配担保 3.提升 4.卡片标记 5.STW

JVM GC 原理 2:分代式垃圾回收算法

在 java 中,大部分对象存在时间很短,小部分对象存活时间长一些,而且存活时间长的对象会存在很长时间。按照二八定律的说法:80%的对象占总存活时间的 20%,剩下 20%的对象存活时间却占了总存活时间的 80%

根据 java 对象的存活规律,可以将jvm 堆空间划分为新生代和老年代。新生代存放刚刚被创建的 java 对象(顾名思义嘛, 新生 代),老年代存放已经存活了一段时间的 java 对象(存活时间长了自然就进入了 老年 )。

根据新生代 java 对象的特点:绝大部分都会在很短时间内变成垃圾,被下一次垃圾回收操作所清除。因此可以给新生代订制一款改进的 复制算法

将新生代所占用的堆空间进一步分为 eden 区和 survivors 区,且 s 区分为 s1 区和 s2 区。e 区空间较大,讲个 s 区空间较小,每次使用 e 区和 s 区的某一个。例如,先使用 e+s1,当没有足够空间时,将 e+s1 中的存活对象放到 s2,然后全部回收 e+s1 的空间,下一个阶段使用 e+s2,就这样依次循环。

在每次新生代垃圾回收时,记录每个对象的存活时长,当超过存活时长阈值(可设定),则该对象将会被放到老年代中。

对于老年代,其中的 java 对象的存活特点是:存活时间长,垃圾率低。因此,在老年代垃圾回收的操作频率会极大降低,而且每次垃圾回收的数量也不多。由于这些特点,老年代一般选择使用 标记-整理算法

全面认识JVM垃圾回收机制

    这里向大家简单介绍一下JVM垃圾回收的相关知识 JVM使用的是分代垃圾回收的方式 可以将Java对象分为 年轻 对象和 年老 对象 JVM将内存堆(Heap)分为两个区域 一个是 年轻 区 另一个是 老 区 Java将这两个区域分别称作是 新生代 和 老生代         JVM垃圾回收的相关知识        JVM使用的是分代垃圾回收的方式 主要是因为在程序运行的时候会有如下特点         ◆大多数对象在创建后很快就没有对象使用它了         ◆大多数在一直被使用的对象很少再去引用新创建的对象         因此就将Java对象分为 年轻 对象和 年老 对象 JVM将内存堆(Heap)分为两个区域 一个是 年轻 区 另一个是 老 区 Java将这两个区域分别称作是 新生代 和 老生代         新生代 区域中 绝大多数新创建的对象都存放在这个区域里 此区域一般来说较小而且JVM垃圾回收频率较高 同时因为 新生代 采用的算法和其存放的对象的特点 使该区域JVM垃圾回收的效率也非常高         而 老生代 区域中存放的是在 新生代 中生存了较长时间的对象 这些对象将被转移到 老生代 区 这个区域一般要大一些而且增长的速度相对于 新生代 要慢一些 老生代 JVM垃圾回收的执行频率也会低很多         由于JVM在JVM垃圾回收处理时会消耗一定的系统资源 因此有时候通过JVM启动的时候添加相关参数来控制 新生代 区域的大小 来调整JVM垃圾回收处理的频率非常有用 以便于我们更合理的利用系统资源         新生代 区域设置参数是 Xmn 用这个参数可以制定 新生代 区域的大小         我们来举一个例子说明         我们就用系统自带的程序作为例子 在命令行上键入如下指令         CDC:\java\demo\jfc\SwingSet 是指 老生代 GC的回收情况 整体堆空间占用从 K降低到 K的水平 用时 秒         通过这些参数的调整我们可以看到在处理垃圾收集问题时 从JVM垃圾回收的频率是时间方面的变化 我们可以根据不同程序的不同情况予以调整         最后有必要提一下GC的相关参数         XX:+PrintGCDetails显示GC的详细信息        XX:+PrintGCApplicationConcurrentTime打印应用执行的时间        XX:+PrintGCApplicationStoppedTime打印应用被暂停的时间        注 : 后的 + 号表示开启此选项 如果是 号那么表示关闭此选项 lishixinzhi/Article/program/Java/hx/201311/25648

JVM垃圾收集机制

JVM垃圾回收机制是java程序员必须要了解的知识,对于程序调优具有很大的帮助(同时也是大厂面试必问题)。

要了解垃圾回收机制,主要从三个方面:

(1)垃圾回收面向的对象是谁?

(2)垃圾回收算法有哪些?

(3)垃圾收集器有哪些?每个收集器有什么特点。

接下来一一讲解清楚:

一、垃圾回收面向的对象

也就是字面意思, 垃圾 回收嘛,重要的是垃圾,那什么对象是垃圾呢,简单来说就是无用的或已死的对象。这样又引申出来什么对象是已死的,怎么判断对象是否已死?

判断对象是否已死有两种算法和对象引用分类:

(1)引用计数算法:

也是字面意思,通过给对象添加引用计数器,添加引用+1,反之-1。当引用为0的时候,此时对象就可判断为无用的。

优点:实现简单,效率高。

缺点:无法解决循环引用(就是A引用B,B也引用A的情况)的问题。

(2)根搜索算法(也就是GC Roots):

通过一系列称为GC Roots的对象,向下搜索,路径为引用链,当某个对象无法向上搜索到GC Roots,也就是成为GC Roots不可达,则为无用对象。

如果一个对象是GC Roots不可达,则需要经过两次标记才会进行回收,第一次标记的时候,会判断是否需要执行finalize方法(没必要执行的情况:没实现finalize方法或者已经执行过)。如果需要执行finalize方法,则会放入一个回收队列中,对于回收队列中的对象,如果执行finalize方法之后,没法将对象重新跟GC Roots进行关联,则会进行回收。

很抽象,对吧,来一个明了的解释?

比如手机坏了(不可达对象),有钱不在乎就直接拿去回收(这就是没实现finalize方法),如果已经修过但是修不好了(已经执行过finalize方法),就直接拿去回收站回收掉。如果没修过,就会拿去维修店(回收队列)进行维修,实在维修不好了(执行了finalize方法,但是无法连上GC Roots),就会拿去回收站回收掉了。

那什么对象可以成为GC Roots呢?

1》虚拟机栈中的引用对象

2》本地方法栈中Native方法引用的对象

2》方法区静态属性引用对象

3》方法区常量引用对象

(3)对象引用分类

1》强引用:例如实例一个对象,就是即使内存不够用了,打死都不回收的那种。

2》软引用:有用非必须对象,内存够,则不进行回收,内存不够,则回收。例如A借钱给B,当A还有钱的时候,B可以先不还,A没钱了,B就必须还了。

3》弱引用:非必须对象,只能存活到下一次垃圾回收前。

4》虚引用:幽灵引用,必须跟引用队列配合使用,目的是回收前收到系统通知。

下面是java的引用类型结构图:

(1)软引用示例

内存够用的情况:

运行结果:

内存不够用的情况:

运行结果:

(2)弱引用示例结果:

无论如何都会被回收

(3)虚引用示例:

运行结果:

解释:为什么2和5的输出为null呢,如下

3为null是因为还没有进行gc,所以对象还没加入到引用队列中,在gc后就加入到了引用队列中,所以6有值。

这个虚引用在GC后会将对象放到引用队列中,所以可以在对象回收后做相应的操作,判断对象是否在引用队列中,可以进行后置通知,类似spring aop的后置通知。

二、垃圾回收发生的区域

垃圾回收主要发生在堆内存里面,而堆内存又细分为 年轻代 老年代 ,默认情况下年轻代和老年代比例为1:2,比如整个堆内存大小为3G,年轻代和老年代分别就是1G和2G,想要更改这个比例需要修改JVM参数-XX:NewRatio,

比如-XX:NewRatio=4,那老年代:年轻代=4:1。而年轻代又分为Eden区,S0(Survivor From)和S1(Survivor To)区,一般Eden:S0:S1=8:1:1,如果想要更改此比例,则修改JVM参数-XX:SurvivorRatio=4,此时就是Eden:S0:S1=4:1:1。

在年轻代发生GC称为Young GC,老年代发生GC成为Full GC,Young GC比Full GC频繁。

解析Young GC:

JVM启动后,第一次GC,就会把Eden区存活的对象移入S0区;第二次GC就是Eden区和S0一起GC,此时会把存活的对象移入S1区,S0清空;第三次GC就是Eden区和S1区进行GC,会把存活的对象移入S0区,如此往复循环15次(默认),就会把存活的对象存入老年区。

类似与如果有三个桶,编号分别为1(1号桶内的沙子是源源不断的,就像工地上。你们没去工地搬过砖可能不知道,但是我真的去工地上搬过啊),2,3。1里面装有沙子,需要将沙子筛为细沙。首先将桶1内的沙子筛选一遍过后的放置于桶2,第二次筛选就会将桶1和桶2里面的沙子一起筛,筛完之后放到桶3内,桶2清空。第三次筛选就会将桶1和桶3的沙子一起筛选,晒完放到桶2内,桶3清空。如此往复循环15次,桶2或桶3里面的沙子就是合格的沙子,就需要放到备用桶内以待使用。

上述中桶1就是Eden区,桶2就是S0区,桶3就是S1区。

三、垃圾回收算法

三种,分别是复制算法,标记-清除算法,标记-整理算法。

(1)复制算法。

其会将内存区域分成同样大小的两块,一块用来使用,另外一块在GC的时候存放存活的对象,然后将使用的一块清除。如此循环往复。

适用于新生代。

优点:没有内存碎片,缺点:只能使用一般的内存。

(2)标记-清除算法。

使用所有内存区域,在GC的时候会将需要回收的内存区域先进行标记,然后同意回收。

适用于老年代。

缺点:产生大量内存碎片,会直接导致大对象无法分配内存。

(3)标记-整理算法。

使用所有内存区域,在GC的时候会先将需要回收的内存区域进行标记,然后将存活对象忘一边移动,最后将清理掉边界以外的所有内存。

适用于老年代。

四、GC日志查看

利用JVM参数-XX:+PrintGCDetails就可以在GC的时候打印出GC日志。

年轻代GC日志:

老年代GC日志:

五、垃圾收集器

主要有四类收集器以及七大收集器

四类:

(1)Serial:单线程收集器,阻塞工作线程,它一工作,全部都得停下。

(2)Paralle:Serial的多线程版本,也是阻塞工作线程

(3)CMS(ConcMarkSweep):并行垃圾收集器,可以和工作线程一起工作。

(4)G1:将堆分成大小一致的区域,然后并发的对其进行垃圾回收。

怎么查看默认的收集器呢?

用JVM参数-XX:+PrintCommandLineFlags,运行之后会输出如下参数。可以看到,jdk1.8默认是Parallel收集器。

七大收集器:

(1)Serial:串行垃圾收集器,单线程收集器。用于新生代。用JVM参数-XX:+UseSerialGC开启,开启后Young区用Serial(底层复制算法),Old区用Serial Old(Serial的老年代版本,底层是标记整理算法)。

(2)ParNew:用于新生代,并行收集器。就是Serial的多线程版本。用JVM参数-XX:+UseParNewGC,young:parnew,复制算法。Old:serialOld,标记整理算法。-XX:ParallecGCThreads限制线程回收数量,默认跟cpu数目一样。只是新生代用并行,老年代用串行。

(3)Parallel Scavenge:并行回收收集器。用JVM参数-XX:+UseParallelGC开启,young:parallel scavenge(底层是复制算法),old:parallel old(parallel的老年代版本,底层是标记整理),新生代老年代都用并行回收器。

这个收集器有两个优点:

可控的吞吐量 :就是工作线程工作90%的时间,回收线程工作10%的时间,即是说有90%的吞吐量。

自适应调节策略 :会动态调节参数以获取最短的停顿时间。

(4)Parallel Old:Parallel Scavenge的老年代版本,用的是标记整理算法。用JVM参数-XX:+UseParallelOldGC开启,新生代用Parallel Scavenge,老年代用Parallel Old

(5)CMS(ConcMarkSweep):并发标记清除。 底层是标记清除算法,所以会产生内存碎片,同时也会耗cpu。 以获取最短回收停顿时间为目标。-XX:+UseConcMarkSweep,新生代用ParNew,老年代用CMS。CMS必须在堆内存用完之前进行清除,否则会失败,这时会调用SerialOld后备收集器。

初始标记和重新标记都会停止工作线程,并发标记和并发清除会跟工作线程一起工作。

(6)SerialOld:老年代串行收集器(以后Hotspot虚拟机会直接移除掉)。

(7)G1:G1垃圾收集器,算法是标记整理,不会产生内存碎片。横跨新生代老年代。实现尽量高吞吐量,满足回收停顿时间更短。

G1可以精确控制垃圾收集的停顿时间,用JVM参数-XX:MaxGCPauseMillis=n,n为停顿时间,单位为毫秒。

区域化内存划片Region,会把整个堆划分成同样大小的区域块(1MB~32MB),最多2048个内存区域块,所以能支持的最大内存为32*2048=65535MB,约为64G。

上图是收集前和收集后的对比,有些对象很大,分割之后就是连续的区域,也即是上图的Humongous。

上述理论可能有点乏味,下图很清晰明了(某度找的)。

下面来一张整个垃圾回收机制的思维导图(太大,分成两部分)。

=======================================================

我是Liusy,一个喜欢健身的程序猿。

欢迎关注【Liusy01】,一起交流Java技术及健身,获取更多干货。

jvm的垃圾回收机制详解

1.JVM的gc概述  gc即垃圾收集机制是指jvm用于释放那些不再使用的对象所占用的内存。java语言并不要求jvm有gc,也没有规定gc如何工作。不过常用的jvm都有gc,而且大多数gc都使用类似的算法管理内存和执行收集操作。  在充分理解了垃圾收集算法和执行过程后,才能有效的优化它的性能。有些垃圾收集专用于特殊的应用程序。比如,实时应用程序主要是为了避免垃圾收集中断,而大多数OLTP应用程序则注重整体效率。理解了应用程序的工作负荷和jvm支持的垃圾收集算法,便可以进行优化配置垃圾收集器。  垃圾收集的目的在于清除不再使用的对象。gc通过确定对象是否被活动对象引用来确定是否收集该对象。gc首先要判断该对象是否是时候可以收集。两种常用的方法是引用计数和对象引用遍历。  1.1.引用计数  引用计数存储对特定对象的所有引用数,也就是说,当应用程序创建引用以及引用超出范围时,jvm必须适当增减引用数。当某对象的引用数为0时,便可以进行垃圾收集。  1.2.对象引用遍历  早期的jvm使用引用计数,现在大多数jvm采用对象引用遍历。对象引用遍历从一组对象开始,沿着整个对象图上的每条链接,递归确定可到达(reachable)的对象。如果某对象不能从这些根对象的一个(至少一个)到达,则将它作为垃圾收集。在对象遍历阶段,gc必须记住哪些对象可以到达,以便删除不可到达的对象,这称为标记(marking)对象。  下一步,gc要删除不可到达的对象。删除时,有些gc只是简单的扫描堆栈,删除未标记的未标记的对象,并释放它们的内存以生成新的对象,这叫做清除(sweeping)。这种方法的问题在于内存会分成好多小段,而它们不足以用于新的对象,但是组合起来却很大。因此,许多gc可以重新组织内存中的对象,并进行压缩(compact),形成可利用的空间。  为此,gc需要停止其他的活动活动。这种方法意味着所有与应用程序相关的工作停止,只有gc运行。结果,在响应期间增减了许多混杂请求。另外,更复杂的 gc不断增加或同时运行以减少或者清除应用程序的中断。有的gc使用单线程完成这项工作,有的则采用多线程以增加效率。2.几种垃圾回收机制  2.1.标记-清除收集器  这种收集器首先遍历对象图并标记可到达的对象,然后扫描堆栈以寻找未标记对象并释放它们的内存。这种收集器一般使用单线程工作并停止其他操作。  2.2.标记-压缩收集器  有时也叫标记-清除-压缩收集器,与标记-清除收集器有相同的标记阶段。在第二阶段,则把标记对象复制到堆栈的新域中以便压缩堆栈。这种收集器也停止其他操作。  2.3.复制收集器  这种收集器将堆栈分为两个域,常称为半空间。每次仅使用一半的空间,jvm生成的新对象则放在另一半空间中。gc运行时,它把可到达对象复制到另一半空间,从而压缩了堆栈。这种方法适用于短生存期的对象,持续复制长生存期的对象则导致效率降低。  2.4.增量收集器  增量收集器把堆栈分为多个域,每次仅从一个域收集垃圾。这会造成较小的应用程序中断。  2.5.分代收集器  这种收集器把堆栈分为两个或多个域,用以存放不同寿命的对象。jvm生成的新对象一般放在其中的某个域中。过一段时间,继续存在的对象将获得使用期并转入更长寿命的域中。分代收集器对不同的域使用不同的算法以优化性能。  2.6.并发收集器  并发收集器与应用程序同时运行。这些收集器在某点上(比如压缩时)一般都不得不停止其他操作以完成特定的任务,但是因为其他应用程序可进行其他的后台操作,所以中断其他处理的实际时间大大降低。  2.7.并行收集器  并行收集器使用某种传统的算法并使用多线程并行的执行它们的工作。在多cpu机器上使用多线程技术可以显著的提高java应用程序的可扩展性。

jvm回收机制的三大算法(三色标记法与垃圾回收器(CMS、G1))

本文编辑:admin

本文相关文章:


jvm回收机制的三大算法(jvm的垃圾回收机制详解)

jvm回收机制的三大算法(jvm的垃圾回收机制详解)

这篇文章给大家聊聊关于jvm回收机制的三大算法,以及jvm的垃圾回收机制详解对应的知识点,希望对各位有所帮助,不要忘了收藏本站哦。本文目录jvm的垃圾回收机制详解JVM垃圾收集机制5、垃圾回收机制三色标记法与垃圾回收器(CMS、G1)jvm

2024年8月2日 03:20

更多文章:


plot3函数的调用格式(在命令窗口用help plot3或用 doc plot3查看plot3的使用方法,观察有何区别)

plot3函数的调用格式(在命令窗口用help plot3或用 doc plot3查看plot3的使用方法,观察有何区别)

本文目录在命令窗口用help plot3或用 doc plot3查看plot3的使用方法,观察有何区别如何MATLAB设置多条曲线的线宽matlab中plot3在实际操作中的用法已知两个线段的端点坐标如何用MATLAB求解他们之间的最短距离

2024年7月10日 09:37

secondary education(post-secondary education 到底是什么意思啊 几岁入学 和其他学习有什么区别)

secondary education(post-secondary education 到底是什么意思啊 几岁入学 和其他学习有什么区别)

本文目录post-secondary education 到底是什么意思啊 几岁入学 和其他学习有什么区别请问在填招聘表的时候secondary education和tertiary education分别是指什么post-secondar

2024年7月4日 08:59

求补码表示的二进制数真值(二进制补码10010011的真值)

求补码表示的二进制数真值(二进制补码10010011的真值)

本文目录二进制补码10010011的真值写出下列用补码表示的二进制数的真值(用十进制表示):二进制补码的运算发生溢出后真值怎么求写出下列各补码的二进制真值已知下列补码求出其真值补码的二进制数真值怎么求,要详细过程 例如(1111 1111

2024年7月18日 11:37

oracle11g补丁包(oracle 11g 补丁下载不了)

oracle11g补丁包(oracle 11g 补丁下载不了)

这篇文章给大家聊聊关于oracle11g补丁包,以及oracle 11g 补丁下载不了对应的知识点,希望对各位有所帮助,不要忘了收藏本站哦。本文目录oracle 11g 补丁下载不了linux oracle 11g装补丁的步骤oracle1

2024年8月2日 06:01

sql查询姓李的同学(找出姓李的sql语句怎么写)

sql查询姓李的同学(找出姓李的sql语句怎么写)

本文目录找出姓李的sql语句怎么写sql 数据库 查询 姓李的以及当天入住的人 select *from 表名 whereSQL数据库解题sql中假定姓名是文本型字段查找姓李的学生使用什么表达式找出姓李的sql语句怎么写select * f

2024年7月23日 14:41

正则表达式不包含数字和字母(正则表达式:数字与字母的混合表达式,不包括纯数字和纯字母)

正则表达式不包含数字和字母(正则表达式:数字与字母的混合表达式,不包括纯数字和纯字母)

大家好,今天小编来为大家解答以下的问题,关于正则表达式不包含数字和字母,正则表达式:数字与字母的混合表达式,不包括纯数字和纯字母这个很多人还不知道,现在让我们一起来看看吧!本文目录正则表达式:数字与字母的混合表达式,不包括纯数字和纯字母必须

2024年7月24日 01:51

vaguely的中文意思(把中文翻译成英文)

vaguely的中文意思(把中文翻译成英文)

本文目录把中文翻译成英文支支吾吾是什么意思,“支支吾吾“的汉语把中文翻译成英文The star-spangled banner I Oh, can you see, by the dawn, we to what, cheers sound

2024年7月12日 06:16

css样式标题居中(div css中怎么将标题居中)

css样式标题居中(div css中怎么将标题居中)

本篇文章给大家谈谈css样式标题居中,以及div css中怎么将标题居中对应的知识点,文章可能有点长,但是希望大家可以阅读完,增长自己的知识,最重要的是希望对各位有所帮助,可以解决了您的问题,不要忘了收藏本站喔。本文目录div css中怎么

2024年8月16日 04:06

revolutionary翻译(文学家,思想家,革命家用英语怎么说)

revolutionary翻译(文学家,思想家,革命家用英语怎么说)

本文目录文学家,思想家,革命家用英语怎么说cheerful ;ratification ;revolutionary 这英语怎么读revolutionary是什么意思请问,革命之路的英文翻译是什么“身体是革命的本钱”的英文翻译革命烈士的英文

2024年7月3日 16:26

domino数据库(如何用把domino数据库中的内容利用ODBC导入到oracle中啊而且domino数据库中的内容如何用Lotus Notes看到)

domino数据库(如何用把domino数据库中的内容利用ODBC导入到oracle中啊而且domino数据库中的内容如何用Lotus Notes看到)

本文目录如何用把domino数据库中的内容利用ODBC导入到oracle中啊而且domino数据库中的内容如何用Lotus Notes看到sql数据库比domino文本数据库好在哪里java调用domino数据库JAVA可以操作domino

2024年7月23日 08:03

什么是交互式图文排版(交互设计的方法)

什么是交互式图文排版(交互设计的方法)

本文目录交互设计的方法什么是交互设计交互设计师是做什么的一个完整的交互设计流程是怎样的indesign 导出是pdf交互格式 和打印格式有什么区别ppt中如何交互设计ui交互设计文字排版有哪几种手法交互设计交互设计与用户体验设计交互总结篇(

2024年7月13日 05:33

java集合排序方法(java 集合中怎么将元素倒序排列)

java集合排序方法(java 集合中怎么将元素倒序排列)

大家好,关于java集合排序方法很多朋友都还不太明白,不过没关系,因为今天小编就来为大家分享关于java 集合中怎么将元素倒序排列的知识点,相信应该可以解决大家的一些困惑和问题,如果碰巧可以解决您的问题,还望关注下本站哦,希望对各位有所帮助

2024年8月6日 23:40

捷达低配后排座椅(捷达VA3座椅怎么调捷达VA3后排放倒图示)

捷达低配后排座椅(捷达VA3座椅怎么调捷达VA3后排放倒图示)

本文目录捷达VA3座椅怎么调捷达VA3后排放倒图示捷达VS5低配座椅配置介绍捷达后排座椅怎么放倒老款捷达后排座有分体的吗捷达VA3座椅怎么调节捷达VA3后排怎么放倒图解捷达VA3最低配内饰怎么样15款捷达后座椅尺寸新捷达轿车,后排座位怎样翻

2024年7月5日 05:29

oracle 触发器 update(oracle更新触发器写法)

oracle 触发器 update(oracle更新触发器写法)

大家好,今天小编来为大家解答以下的问题,关于oracle 触发器 update,oracle更新触发器写法这个很多人还不知道,现在让我们一起来看看吧!本文目录oracle更新触发器写法oracle update触发器如何获取被修改的字段or

2024年9月1日 00:00

hacknet80端口(hacknet nortron安全网络服务器怎么破)

hacknet80端口(hacknet nortron安全网络服务器怎么破)

“hacknet80端口”相关信息最新大全有哪些,这是大家都非常关心的,接下来就一起看看hacknet80端口(hacknet nortron安全网络服务器怎么破)!本文目录hacknet nortron安全网络服务器怎么破hacknet

2024年8月6日 03:55

java自学需要多久(想学Java开发,一般需要学多久呀)

java自学需要多久(想学Java开发,一般需要学多久呀)

本文目录想学Java开发,一般需要学多久呀学JAVA大概需要多长时间学java要学多久想学Java开发,一般需要学多久呀目前市面上的java培训,学习的时间基本上在4-6个月不等,具体的根据不同的培训学校,学习时间的长短会有差异。那么市面上

2024年7月2日 18:20

java最浪漫的编程代码(求JAVA编程代码)

java最浪漫的编程代码(求JAVA编程代码)

本文目录求JAVA编程代码Java编程求代码急求一个个JAVA编程简单程序代码java 编程代码求Java编程代码求java编程代码 3种以上求JAVA编程代码答案放在下面地址:***隐藏网址***顺便给我踩下空间^-^本小牛听了那六级大牛

2024年7月17日 03:29

cstring转const char 函数(cannot convert parameter 1 from ’CString’ to ’const char *’)

cstring转const char 函数(cannot convert parameter 1 from ’CString’ to ’const char *’)

各位老铁们,大家好,今天由我来为大家分享cstring转const char 函数,以及cannot convert parameter 1 from ’CString’ to ’const char *’的相关问题知识,希望对大家有所帮助

2024年8月18日 11:45

《高级家庭课程》2在线(高端家庭教育的内容是什么)

《高级家庭课程》2在线(高端家庭教育的内容是什么)

大家好,关于《高级家庭课程》2在线很多朋友都还不太明白,不过没关系,因为今天小编就来为大家分享关于高端家庭教育的内容是什么的知识点,相信应该可以解决大家的一些困惑和问题,如果碰巧可以解决您的问题,还望关注下本站哦,希望对各位有所帮助!本文目

2024年7月31日 14:25

ecshop版权信息设置(修改ECSHOP头部版权报错)

ecshop版权信息设置(修改ECSHOP头部版权报错)

本文目录修改ECSHOP头部版权报错ecshop3.0版权如何去掉ECSHOP页脚灰色字体版权信息怎么修改如何去除ecshop标题和网站底部的Powered by ECShop修改ECSHOP头部版权报错版权信息是在一个JS文件里的。。您是

2024年7月14日 06:07

近期文章

本站热文

iphone vpn设置(ios设置vpn快捷开关)
2024-07-22 15:01:12 浏览:2334
windows12正式版下载(操作系统Windows Server 2012 R2,在哪能下载到,公司用的)
2024-07-20 17:26:53 浏览:1730
java安装教程(win10如何安装JAVA)
2024-07-19 19:55:49 浏览:1154
client mfc application未响应(每次进cf就提示client MFC Application未响应该怎么办啊!急急急)
2024-07-20 11:15:58 浏览:1151
标签列表

热门搜索