JVM垃圾回收系列之垃圾收集器二

news2024/11/27 18:29:13

随笔

最近两个星期因为要忙公司项目上线的事情以至于发表的文章会显得碌碌庸流,在此以示歉意
在这里插入图片描述

引言

本文将介绍HotSpot中的G1GC

参考书籍:“深入理解Java虚拟机”

个人java知识分享项目——gitee地址

个人java知识分享项目——github地址

G1GC

介绍

G1(Garbage First)是一款面向服务器应用的垃圾收集器,主要针对配备多核CPU及大容量内存的机器,以极高概率满足GC停顿时间的同时,还兼具高吞吐量的性能特性。在JDK1.7版本正式启用,移除了Experimental标识,是JDK 9以后的默认垃圾回收器,取代了CMS回收器以及Parallel+ParallelOld组合。被Oracle官方称为“全功能的垃圾收集器”。与此同时,CMS已经在JDK 9中被标记为废弃(deprecated)。在JDK8中还不是默认的垃圾回收器,需要使用-XX:UseG1GC来启用。HotSpot垃圾收集器里,除了G1以外,其他的垃圾收集器使用内置的JVM线程执行GC的多线程操作,而G1 GC可以采用应用线程承担后台运行的GC工作,即当JVM的GC线程处理速度慢时,系统会调用应用线程帮助加速垃圾回收过程。

疑问:

1.既然我们已经有了前面几个强大的GC,为什么还要发布Garbage First(G1)GC?

答: 原因就在于应用程序所应对的业务越来越庞大、复杂,用户越来越多,没有GC就不能保证应用程序正常进行,而经常造成STW的GC又跟不上实际的需求,所以才会不断地尝试对GC进行优化。G1(Garbage First)垃圾回收器是在Java 7 update 4之后引入的一个新的垃圾回收器,是当今收集器技术发展的最前沿成果之一。与此同时,为了适应现在不断扩大的内存和不断增加的处理器数量,进一步降低暂定时间(Pause Time),同时兼顾良好的吞吐量。官方给G1设定的目标是在延迟可控的情况下获得尽可能高的吞吐量,所以才当起“全功能收集器”的重任与期望。

2.为什么名字叫做Garbage First(G1)呢?

答: 因为G1是一个并行回收器,它把堆内存分割为很多不相关的区域(Region)(物理上不连续的)。使用不同的Region来表示Eden、幸存者0区、幸存者1区,老年代等。G1 GC有计划地避免在整个Java堆中进行全区域的垃圾收集。G1跟踪各个Region里面的垃圾堆积的价值大小(回收所获得的空间大小以及回收所需时间的经验值),在后台维护一个优先列表,每次根据允许的收集时间,优先回收价值长的Region。由于这种方式的侧重点在于回收垃圾最大量的区间(Region),所以我们给G1一个名字:垃圾优先(GarbageFirst)

特点

G1是一款面向服务端应用的垃圾收集器。HotSpot开发团队赋予它的使命是(在比较长期的)未来可以替换掉JDK 1.5中发布的CMS收集器。与其他GC收集器相比,G1具备如下特点:

  • 并行与并发
    • 并行性:G1在回收期间,可以有多个GC线程同时工作,有效利用多核计算能力。此时用户线程STW
    • 并发性:G1拥有与应用程序交替执行的能力,部分工作可以和应用程序同时执行,因此,一般来说,不会在整个回收阶段发生完全阻塞应用程序的情况。
  • 分代收集
    • 从分代上看,G1依然属于分代型垃圾回收器,它会区分年轻代和老年代,年轻代依然有Eden去和Survivor区。但从堆的结构上看,它不要求整个Eden区、年轻代或者老年代都是连续的,也不再坚持固定大小和固定数量。
    • 将堆空间分为若干个区域(Region),这些区域中包含了逻辑上的年轻代和老年代。
    • 和之前的各类回收器不同,它同时兼顾年轻代和老年代。对比其他回收器,或者工作在年轻代,或者工作在老年代;
  • 空间整合
    • CMS:“标记-清除”算法、内存碎片、若干次后进行一次碎片整理
    • G1将内存划分为一个个的region。内存的回收是以region作为基本单位的。Region之间是复制算法,但整体上实际可看作是标记-压缩(Mark-Compact)算法,两种算法都可以避免内存碎片。这种特性有利于程序长时间运行,分配大对象时不会因为无法找到连续内存空间而提前触发下一次GC。尤其是当Java堆非常大的时候,G1的优势更加明显。
  • 可预测的停顿时间模型(即:软实时soft real-time)
    • 这是G1相对于CMS的另一大优势,G1除了追求停顿外,还能建立可预测的停顿时间模型,能让使用者明确指定在一个长度为M毫秒的时间片段内,消耗在垃圾收集上的时间不得超过N毫秒。由于分区的原因,G1可以只选取部分区域进行内存回收,这样缩小了回收的范围,因此对于全局停顿情况的发生也能得到较好的控制,G1跟踪各个Region里面的垃圾堆积的价值大小(回收所获取的空间大小以及回收所需时间的经验值),在后台维护一个优先列表,每次根据允许的收集时间,优先回收价值最大的Region。保证了G1收集器在有限的时间内可以获取尽可能高的收集效率。相比于CMS GC,G1未必能做到CMS在最好情况下的延时停顿,但是最差情况要好很多。

在G1之前的其他收集器进行收集的范围都是整个新生代或者老年代,而G1不再是这样。使用G1收集器时,Java堆的内存布局就与其他收集器有很大差别,它将整个Java堆划分为多个大小相等的独立区域(Region),虽然还保留有新生代和老年代的概念,但新生代和老年代不再是物理隔离的了,它们都是一部分Region(不需要连续)的集合。

G1收集器之所以能建立可预测的停顿时间模型,是因为它可以有计划地避免在整个Java 堆中进行全区域的垃圾收集。G1跟踪各个Region里面的垃圾堆积的价值大小(回收所获得的空间大小以及回收所需时间的经验值),在后台维护一个优先列表,每次根据允许的收集时间,优先回收价值最大的Region(这也就是Garbage-First名称的来由)。这种使用Region划分内存空间以及有优先级的区域回收方式,保证了G1收集器在有限的时间内可以获取尽可能高的收集效率。

分区Region:化整为零

使用G1收集器时,它将整个Java堆划分成约2048个大小相同的独立Region块,每个Region块大小根据堆空间的实际大小而定,整体被控制在1MB到32MB之间,且为2的N次幂,即1MB,2MB,4MB,8MB。可以通过-XX:G1HeapRegionSize设定。所有的Region大小相同,且在JVM生命周期内不会改变。

虽然还保留有新生代和老年代的概念,但新生代和老年代不再是物理隔离的了,他们都是一部分Region(不需要连续)的集合。通过Region的动态分配方式实现逻辑上的连续。一个region有可能属于Eden,Survivor或者Old/Tenured内存区域。但是一个region只可能属于一个角色。E表示该region属于Eden内存区域,s表示属于Survivor内存区域,o表示Old内存区域。一个region有可能属于Eden,Survivor或者Old/Tenured内存区域。但是一个region只可能属于一个角色。E表示该region属于Eden内存区域,s表示属于Survivor内存区域,o表示Old内存区域。G1垃圾收集器还增加了一种新的内存区域,叫做Humongous内存区域。主要用来存储大对象,如果超过1.5个region,就放到H。

设置H的原因:

对于堆中的大对象,默认直接会被分配到老年代,但是如果它是一个短期存在的大对象,就会对垃圾收集器造成负面影响。为了解决这个问题,G1划分了一个Humongous区,它用于专门存放大对象。如果一个H区装不下一个大对象,那么G1会寻找连续的H区作为老年代的一部分来看待。为了能找到连续的H区,有时候不得不启动Full GC。G1的大多数行为都把H区作为老年代的一部分来看待。

G1把内存“化整为零”的思路,理解起来似乎很容易,但其中的实现细节却远远没有想象中那样简单,否则也不会从2004年Sun实验室发表第一篇G1的论文开始直到今天(将近10年 时间)才开发出G1的商用版。笔者以一个细节为例:把Java堆分为多个Region后,垃圾收集是否就真的能以Region为单位进行了?听起来顺理成章,再仔细想想就很容易发现问题所 在:Region不可能是孤立的。一个对象分配在某个Region中,它并非只能被本Region中的其 他对象引用,而是可以与整个Java堆任意的对象发生引用关系。那在做可达性判定确定对象是否存活的时候,岂不是还得扫描整个Java堆才能保证准确性?这个问题其实并非在G1中才有,只是在G1中更加突出而已。在以前的分代收集中,新生代的规模一般都比老年代要小许多,新生代的收集也比老年代要频繁许多,那回收新生代中的对象时也面临相同的问题,如果回收新生代时也不得不同时扫描老年代的话,那么Minor GC的效率可能下降不少。

在G1收集器中,Region之间的对象引用以及其他收集器中的新生代与老年代之间的对象引用,虚拟机都是使用Remembered Set来避免全堆扫描的。G1中每个Region都有一个与之对应的Remembered Set,虚拟机发现程序在对Reference类型的数据进行写操作时,会产生一个 Write Barrier暂时中断写操作,检查Reference引用的对象是否处于不同的Region之中(在分代 的例子中就是检查是否老年代中的对象引用了新生代中的对象),如果是,便通过 CardTable把相关引用信息记录到被引用对象所属的Region的Remembered Set之中。当进行内存回收时,在GC根节点的枚举范围中加入Remembered Set即可保证不对全堆扫描也不会有遗漏。

垃圾回收过程

G1 GC的垃圾回收过程主要包括如下三个环节(忽略Remembered Set过程):

  1. 年轻代GC(Young GC)
  2. 老年代并发标记过程(Cocurrent Marking)
  3. 混合回收(Mixed GC)(如果需要,单线程、独占式、高强度的Full GC还是继续存在的。它针对GC的评估失败提供了一种失败保护机制,即强力回收。)

应用程序分配内存,当年轻代的Eden区用尽时开始年轻代回收过程:G1的年轻代收集阶段是一个并行的独占式收集器。在年轻代回收器,G1 GC暂停所有应用程序线程,启动多线程执行年轻代回收。然后从年轻代区间移动存活对象到Survivor区间或者老年区间,也可能是两个区间都会涉及。当堆内存使用达到一定值(默认45%)时,开始老年代并发标记过程。标记完成马上开始混合回收过程。对于一个混合回收器,G1 GC从老年区间移动到存活对象到空闲区间,这些空闲区间也就成为了老年代的一部分。和年轻代不同,老年代的G1回收器和其他GC不同,G1的老年代回收器不需要整个老年代被回收,一次只需要扫描/回收一小部分老年代的Region就可以了。同时,这个老年代Region是和年轻代一起被回收的。举个例子:一个Web服务器,Java进程最大堆内存为4G,每分钟响应1500个请求,没45秒钟会新分配大约2G的内存。G1会每45秒钟进行一次年轻代回收,每32个小时整个堆的使用率会达到45%,会开始老年代并发标记过程,标记完成后开始四到五次的混合回收。

G1回收过程一:年轻代GC
  • 第一阶段,扫描根
    • 根是指static变量指向的对象,正在执行的方法调用链条上的局部变量等。根引用连同RSet记录的外部引用作为扫描存活对象的入口。
  • 第二阶段,更新RSet
    • 处理dirty card queue(见备注)中的card,更新RSet。次阶段完成后,RSet可以准确的反映老年代对所在的内存分段中对象的引用
    • 对于应用程序的引用赋值语句Object.field = object ,JVM会在之前和之后执行特殊的操作以在dirty card queue中入队一个保存了对象引用信息的card。在年轻代回收的时候,G1会对Dirty Card Queue中所有的Card进行处理,以更新RSet,保证RSet实时准确的反应引用关系
    • 那为什么不在引用赋值语句处直接跟新RSet呢?这是为了性能的需要,RSet的处理需要线程同步,开销会很大,使用队列性能会好很多。
  • 第三阶段,处理RSet
    • 识别被老年代对象指向的Eden中的对象,这些被指向的Eden中的对象被认为是存活的对象
  • 第四阶段,复制阶段
    • 此阶段,对象树被遍历,Eden区内存段中存活的对象会被复制到Surviror区中空的内存分段Survivor区内存段中存活的对象如果年龄未达阈值,年龄会加1,达到阈值会被复制到Old区中空的内存分段。如果Survivor空间不够,Eden空间的部分数据会直接晋升到老年代空间。
  • 第五阶段,处理引用
    • 处理Soft,Weak,Phantom,Final,JNI Weak等引用。最终Eden空间的数据为空,GC停止工作,而目标内存中的对象都是连续存储的,没有碎片,所以复制过程可以达到内存整理的效果,减少碎片。
G1回收过程二:并发标记过程
  • 初始标记阶段:标记从根节点直接可达的对象。这个阶段是STW的,并且会触发一次年轻代GC。
  • 根区域扫描(Root Region Scanning):G1 GC扫描Survivor区直接可达的老年代区域对象,并标记被引用的对象。这一过程必须在Young GC之前完成。
  • 并发标记(Concurrent Marking):在整个堆中进行并发标记(和应用程序并发执行),此过程可能被Young GC中断。在并发标记阶段,若发现区域对象中的所有对象都是垃圾,那这个区域会被立即回收。同时,并发标记过程中,会计算每个区域的对象活性(区域中存活对象的比例)。
  • 再次标记(Remarking):由于应用程序持续进行,需要修正上一次的标记结果。是STW的。G1中采用了比CMS更快的初始快照算法:snapshot-at-the-beginning(SATB)。
  • 独占清理(cleanup,STW):计算各个区域的存活对象和GC回收比例,并进行排序,识别可以混合回收的区域。为下阶段做铺垫。是STW的。
    • 这个阶段并不会实际上去做垃圾的收集。
  • 并发清理阶段:识别并清理完全空闲的区域。
G1回收过程三:混合回收

并发标记结束以后,老年代中百分百为垃圾的内存分段被回收了,部分为垃圾的内存分段被计算了出来,默认情况下,这些老年代的内存分段会分8次(可以通过-XX:G1MixedGCCountTarget设置)被回收。混合回收的回收集(Collection Set)包括八分之一的老年代内存分段,Eden区内存分段,Survivor区内存分段。混合回收的算法和年轻代回收的算法完全一样,只是回收集多了老年代的内存分段。具体过程请参考上面的年轻代回收过程。由于老年代中的内存分段默认分8次回收,G1会优先回收垃圾多的内存分段。垃圾占内存分段比例越高的,越会被先回收。并且有一个阈值会决定内存分段是否被回收,-XX:G1MixedGCLiveThresholdPercent默认为65%,意思是垃圾内存分段比例要达到65%才会被回收。如果垃圾占比太低,意味着存活的对象占比高,在赋值的时候会花费更多的时间。混合回收并不一定要进行8次。有一个阈值-XX:G1HeapWastePercent,默认值为10%,意思是允许整个堆内存中有10%,则不再进行混合回收。因为GC会花费很多的时间但是回收到的内存却很少。

G1回收过程四:Full GC(可选过程)

G1的初衷就是要避免Full GC的出现。但是如果上述方式不能正常工作,G1会停止应用程序的执行(Stop-The-World),使用单线程的内存回收算法进行垃圾回收,性能会非常差,应用程序停顿时间会很长。要避免Full GC的发生,一旦发生需要进行调整。什么时候会发生Full GC呢?比如堆内存太小,当G1在复制存活对象的的时候没有空的内存分段可用,则会回退到Full GC,这种情况可以通过增大内存解决。

导致G1Full GC的原因可能有两个:

  • Evacuation的时候没有足够的to-space来存放晋升的对象;
  • 并发处理过程完成之前空间耗尽。

再贴上“深入理解java虚拟机”中对于G1收集器的运行图:
在这里插入图片描述

从上述对G1的介绍内容来看,G1毋庸置疑是非常强大的,但是它也并不是万能的垃圾收集器,相比较于CMS,G1还不具备全方位、压倒性优势。比如在用户程序运行过程中,G1无论是为了垃圾收集产生的内存占用(Footprint)还是程序运行时的额外负载(Overload)都要比CMS要高。从经验上来说,在小内存应用上CMS的表现大概率会优于G1,而G1在大内存应用上则发挥其优势。平衡点在6-8GB之间。

G1回收器的参数设置:

  • -XX:+UseG1GC 手动指定使用G1收集器执行内存回收任务。
  • -XX:G1HeapRegionSize 设置每个Region的大小。值是2的幂,范围是1MB到32MB之间,目标是根据最小的Java堆大小划分出约2048个区域。默认是堆内存的1/2000。
  • -XX:MaxGCPauseMillis 设置期望达到的最大GC停顿时间指标(JVM会尽力实现,但不保证达到)。默认值是200ms
  • -XX:ParallelGCThread 设置STW工作线程数。将n设置为并行垃圾回收线程数(ParralelGCThread)的1/4左右。
  • -XX:InitiationHeapOccupancyPercent 设置触发并发GC周期的Java堆占用率阈值。超过此值,就触发GC。默认值是45

G1回收器的适用场景:

  • 面向服务端应用,针对具有大内存、多处理器的机器。(在普通大小的堆里表现并不惊喜)
  • 最主要的应用是需要低GC延迟,并具有大堆的应用程序提供解决方案。如:在堆大小约6GB或更大时,可预测的暂停时间可以低于0.5秒;(G1通过每次只清理一部分而不是全部的Region的增量式清理来保证每次GC停顿时间不会过长)。
  • 用来替换掉JDK1.5中的CMS收集器,在下面的情况下,使用G1可能比CMS好:
    • 超过50%的Java堆被活动数据占用;
    • 对象分配频率或年代提升频率变化很大;
    • GC停顿时间过长(长于0.5至1秒)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/3900.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

双向链表的操作

什么是双向链表? 指针域:用于指向当前节点的直接前驱节点; 数据域:用于存储数据元素。 指针域:用于指向当前节点的直接后继节点; typedef struct line{struct line * prior; //指向直接前趋,结…

超级简单的机器学习入门

超级简单的机器学习入门 文章目录超级简单的机器学习入门0.写在前面1.机器学习基本概念2.机器学习算法的类型2.1 监督学习2.2 无监督学习2.3 监督学习和无监督学习的对比2.4 强化学习3.机器学习的三个基本要素3.1 模型3.2 学习准则3.2.1 损失函数3.2.2 欠拟合和过拟合&#xff…

MySQL数据库 || 增删改查操作详解

目录 前言: 插入数据 查询数据 全列查询 指定列查询 带表达式查询 去重查询 查询结果排序 条件查询 比较运算符 逻辑运算符 示例 模糊查询 示例 空值比较 分页查询 修改数据 删除数据 注意: 前言: 🎈增删改查…

Flutter——常用布局

Flutter—常用布局效果图widget 树形图左布局Text评分条提示内容右布局应用Stack布局效果图释示例效果图释电影封面电影信息电影演员电影简介应用效果图 widget 树形图 整个界面由一行组成,分为两列;左列包括电影介绍,由上到下垂直排列&…

java计算机毕业设计ssm+jsp线上授课系统

项目介绍 通篇文章的撰写基础是实际的应用需要,然后在架构系统之前全面复习线上授课的相关知识以及网络提供的技术应用教程,以线上授课的实际应用需要出发,架构系统来改善现线上授课工作流程繁琐等问题。不仅如此以操作者的角度来说&#xf…

【JavaSE】关于数组

文章目录数组的创建与初始化数组的初始化静态初始化动态初始化数组的存储null打印数组的三种方式循环遍历打印foreach打印Arrays.toString()打印数组的练习冒泡排序常用的API数组拷贝Arrays.copyOf()数组排序Arrays.sort()数组的快速初始化Arrays.fill()二维数组数组的创建与初…

mysql之MHA的高可用

一、MHA概述 1.什么是 MHA: MHA(MasterHigh Availability)是一套优秀的MySQL高可用环境下故障切换和主从复制的软件。 MHA 的出现就是解决MySQL 单点故障的问题。 MySQL故障切换过程中,MHA能做到0-30秒内自动完成故障切换操作…

1分钟完成在线测试部署便捷收集班级同学文件的web管理系统

最近CSDN推出了一个新功能【云IDE】,个人对这个新功能(比赛奖金 )挺感兴趣的🤭,于是瞬速地拿之前自己搞的一个便捷收集班级同学文件的web管理系统(下面简称该项目为cfile)体验了一下,发现功能还是挺好用的&…

Node.js 实战 第1章 欢迎进入Node.js 的世界 1.5 三种主流的Node 程序 1.6 总结

Node.js 实战 文章目录Node.js 实战第1章 欢迎进入Node.js 的世界1.5 三种主流的Node 程序1.5.1 Web 应用程序1.5.2 命令行工具和后台程序1.5.3 桌面程序1.5.4 使用Node 的应用程序1.6 总结第1章 欢迎进入Node.js 的世界 1.5 三种主流的Node 程序 Node 程序主要可以分成三种类…

某网站视频播放花屏解密

某网站视频播放花屏解密样例网址:aHR0cHM6Ly90di5jY3R2LmNvbS8yMDIyLzA5LzMwL1ZJREVnZ0ZRYmZ6NmlMeXZjN0F4d0NlZjIyMDkzMC5zaHRtbA 站内之前也曾经发过相关的问题 1.CCTV视频m3u8视频下载,下载下来时长正确,有声音,但是画面是马…

聚沙成塔【45天玩转uni-app】初探uni-app

文章目录写在前面DCloud当下跨平台开发存在的问题为什么选择uni-app写在最后写在前面 聚沙成塔——每天进步一点点,大家好我是几何心凉,不难发现越来越多的前端招聘JD中都加入了uni-app 这一项,它也已经成为前端开发者不可或缺的一项技能了&…

ROS1可视化利器---Webviz

0. 简介 对于ROS1而言,rqt和plotjuggler是我们最常用的工具,这两个工具:rqt中嵌入了很多有用的小工具,但是它需要播放离线包,没有办法对离线包进行实时的分析。而plotjuggler支持对离线bag包进行分析,但是…

[C语言、C++]数据结构作业:用递归实现走迷宫(打印正确通路即可)

如果是非递归情况 如果当前点(方格)为出口,则成功退出 (否则) 如果可继续走(向相邻点试探),存在某个可前进 的相邻点(分支)则: 1、将当前点保存,以便回退 2、将相邻点作为当前点…

【数据结构】排序3——交换排序(冒泡排序、快速排序)

文章目录交换排序冒泡排序冒泡排序算法算法分析快速排序改进后的快速排序算法算法分析交换排序 【基本思想】 两两比较,如果发生逆序则交换,直到所有记录都排好序为止。 常见的交换排序方法: 冒泡排序T(n)O(n2) 快速排序T(n)O( nlog2n) 冒…

SpringSecurity Oauth2实战 - 06 获取用户登录信息并存储到本地线程

文章目录1. 获取用户登录信息1. 用户信息共享的ThreadLocal类 UserInfoShareHolder2. 写一个拦截器 UserInfoInterceptor3. 配置拦截器 CommonWebMvcAutoConfiguration2. 源码分析1. 认证用户通过access_token访问受限资源2. 进入过滤器 OAuth2AuthenticationProcessingFilter#…

源码分析:Transport 开发

有关 transport 相关队列的调用过程: 【T ransportService 】 TransportService.java 的所有接口由 DefaultTransportService.java 实现,里面包含四种接口: (1)发送到 ruleEngine 发送 TbProtoQueueMsg<ToRuleEngineMsg> 消息。 由 DefaultTbRuleEngineConsumerServic…

【攻破css系列——附加篇】vscode自动格式化

文章目录1. 快速格式化1.1 格式化的定义1.2 vscode的格式化组合键2. 自动格式化2.1 定义2.2 设置自动格式化的步骤1. 快速格式化 1.1 格式化的定义 格式化会让我们的代码正确缩进&#xff0c;同级标签的缩进空格一致&#xff0c;最后使我们代码更好看且易懂。 没有格式化我们…

Nginx学习

Nginx学习 nginx的基本概念 nginx是什么&#xff1f;做什么事情&#xff1f; Nginx (engine x) 是一个高性能的HTTP和反向代理web服务器&#xff0c;特点是占用内存少&#xff0c;并发能力强&#xff0c;事实上nginx的并发能力确实在同类型的网页服务器中表现较好。nginx专为…

gcc编程4步编译、调试c程序实操详解(Linux系统编程)

gcc编译可以执行程序4步骤:预处理、编译、汇编、链接 一、知识储备&#xff08;想看实战往下翻&#xff09; 在linux程序种&#xff0c;c程序需要用gcc进行编译&#xff0c;链接用ld程序&#xff0c;ggc编译完成后可自动调用ld程序完成链接。 调用gcc程序的语法格式&#xff…

Android Studio APP开发入门之对话框Dialog的讲解及使用(附源码 包括提醒对话框,日期对话框,时间对话框)

运行有问题或需要源码请点赞关注收藏后评论区留言~~~ 一、提醒对话框 AlertDialog名为提醒对话框&#xff0c;它是Android中最常用的对话框&#xff0c;可以完成常见的交互操作&#xff0c;例如提示&#xff0c;确认&#xff0c;选择等功能&#xff0c;由于AlertDialog没有公开…