Java性能权威指南-总结7

news2024/11/13 8:03:27

Java性能权威指南-总结7

  • 垃圾收集算法
    • 理解Throughput收集器
    • 堆大小的自适应调整和静态调整
    • 理解CMS收集器

垃圾收集算法

理解Throughput收集器

Throughput收集器有两个基本的操作;其一是回收新生代的垃圾,其二是回收老年代的垃圾。
下图展示了堆在新生代回收之前和回收之后的情况:在这里插入图片描述
通常新生代的垃圾回收发生在Eden空间快用尽时。新生代垃圾收集会把Eden空间中的所有对象挪走:一部分对象会被移动到Survivor空间(即这幅图中的S0区域),其他的会被移动到老年代;回收之后老年代中保存了更多的对象。当然,还有大量的对象因为没有任何对象引用而被回收。

开启了PrintGCDetails标志的GC日志中,Minor GC形式如下:

17.806:[GC [PSYoungGen:227983K->14463K(264128K)]
					280122K->66610K(613696K),0.0169320 secs]
					[Times: user=0.05 sys=0.00,real=0.02 secs]

这次GC在程序开始运行17.806秒后发生。现在新生代中对象占用的空间为14463 KB(约为14 MB,位于Survivor空间内);GC之前,新生代对象占用的空间为227983 KB(约为227 MB)。(实际上,227893 KB严格折算只有222 MB,为了便于讨论,以1000为单位将它们折算到KB。)新生代这时总的大小为264 MB。

与此同时,堆的空间总的使用情况(包含新生代和老年代)从280 MB减少到了66 MB,这个时刻整个堆的大小为613 MB。完成垃圾回收操作耗时0.02秒(排在输出最后的Real时间是0.0169320秒——实际时间进行了归整)。程序消耗的CPU时间比Real时间往往更多,原因是新生代垃圾回收会使用多个线程。
下图展示了Full GC之前及之后堆的使用情况:
在这里插入图片描述
老年代垃圾收集会回收新生代中所有的对象(包括Survivor空间中的对象)。只有那些有活跃引用的对象,或者已经经过压缩整理的对象(它们占据了老年代的开始部分)会在老年代中继续保持,其余的对象都会被回收。

Full GC的日志输出示例如下:

64,546:[FuLl GC [PSYoungGen: 15808K->0K(339456K)]
				[ParoldGen:457753K->392528K(554432K)] 473561K->392528K(893888K)
				[PSPermGen: 56728K->56728K(115392K)],1.3367880 secs]
				[Times: user=4.44 sys=0.01,real=1.34 secs]

新生代的空间使用在经历Full GC之后变为0字节(新生代的大小为339 MB)。老年代中的空间使用从457MB减少到了392MB,因此整个堆的使用从473 MB减少到了392 MB。永久代空间的使用没有发生变化;在多数的Full GC中,永久代的对象都不会被回收。(如果永久代空间耗尽,JVM会发起FulI GC回收永久代中的对象,这时会观察到永久代空间的变化——这是永久代进行回收唯一的情况。这个例子使用的是Java 7;在Java 8中,类似的信息可以在元空间中找到)。由于Full GC要进行大量的工作,所以消耗了约1.3秒的Real时间,4.4秒的CPU时间(同样源于使用了多个并行的线程)。

快速小结

  1. Throughput收集器会进行两种操作,分别是Minor GC和Full GC。
  2. 通过GC日志中的时间输出,我们可以迅速地判断出Throughput收集器的GC操作对应用程序总体性能的影响。

堆大小的自适应调整和静态调整

Throughput收集器的调优几乎都是围绕停顿时间进行的,寻求堆的总体大小、新生代的大小以及老年代大小之间平衡。

考虑Throughput收集器的调优方案时有两种取舍。首先比较经典的是编程技术上的取舍,即时间与空间的取舍。第二个取舍与完成垃圾回收所需的时长相关。增大堆能够减少Full GC停顿发生的频率,但也有其局限性:由于GC时间变得更长,平均响应时间也会变长。类似地,为新生代分配更多的堆空间可以缩短Full GC的停顿时间,不过这又会增大老年代垃圾回收的频率(因为老年代空间保持不变或者变得更小了)。

下图展示了采用这些取舍的效果。图上显示的是运行在GlassFish实例上的股票Servlet应用,在使用不同大小的堆时,最大吞吐量的变化情况。使用256 MB的小堆时,应用服务器在垃圾回收上消耗了大量的时间(实际消耗的时间高达总时间的36%);吞吐量因此受到限制,比较低。随着堆大小的增加,吞吐量迅速提升——直到堆的容量增大到1500MB。这之后吞吐量的增速迅速减缓,这时应用程序实际已经不太受垃圾回收的影响(垃圾回收消耗的时间仅仅只占总时间的6%左右)。收益递减规律逐渐凸显出来:虽然应用程序可以通过增加内存的方式提升吞吐量,不过其效果已经很有限了。

堆的大小达到4500MB后,吞吐量开始出现少量下滑。这时,应用程序面临着第二个选择:增加的内存导致GC周期愈加冗长,虽然它们发生的频率小得多,但这些超长的GC周期也会影响系统整体的吞吐量。

这幅图中的数据取自关闭了自适应调整的JVM;它的最大、最小堆的容量设置成了同样的大小。对任何一种应用,都可以通过实验确定堆和代的最佳大小,但是,让JVM自己来选择通常是更容易的方法(这也是最通常的做法,因为默认情况下自适应调整就是开启的)。
在这里插入图片描述
为了达到停顿时间的指标,Throughput收集器的自适应调整会重新分配堆(以及代)的大小。使用这些标志可以设置相应的性能指标:-XX:MaxGCPauseMillis=N-XX:GCTimeRatio=N

快速小结

  1. 采用动态调整是进行堆调优极好的入手点。对很多的应用程序而言,采用动态调整就已经足够,动态调整的配置能够有效地减少JVM的内存使用。
  2. 静态地设置堆的大小也可能获得最优的性能。设置合理的性能目标,让JVM根据设置确定堆的大小是学习这种调优很好的入门课程。

理解CMS收集器

CMS收集器有3种基本的操作,分别是:

  • CMS收集器会对新生代的对象进行回收(所有的应用线程都会被暂停);
  • CMS收集器会启动一个并发的线程对老年代空间的垃圾进行回收;
  • 如果有必要,CMS会发起Full GC。

下图展示了使用CMS回收新生代的情况。
在这里插入图片描述
CMS收集器的新生代垃圾收集与Throughput收集器的新生代垃圾收集非常相似:对象从Eden空间移动到Survivor空间,或者移动到老年代空间。CMS收集的GC日志也非常相似:

89.853:[GC 89.853:[ParNew: 629120K->69888K(629120K),0.1218970 secs]
				1303940K->772142K(2027264K),0.1220090 secs]
				[Tines: user=0.42 sys=0.02,real=0.12 secs]

这时的新生代空间大小为629MB;垃圾回收之后变成了69 MB(位于Survivor空间)。与Throughput收集器的日志类似,整个堆的大小为2027 MB,其中772 MB在垃圾回收之后依然被占用。虽然并行的GC线程使用了0.42秒的CPU时间,但整个垃圾回收过程仅耗时0.12秒。并发的垃圾回收周期如下面的图所示。
在这里插入图片描述

JVM会依据堆的使用情况启动并发回收。当堆的占用达到某个程度时,JVM会启动后台线程扫描堆,回收不用的对象。扫描结束的时候,堆的状况就像这幅图中最后一列所描述的情况一样。请注意,如果使用CMS回收器,老年代空间不会进行压缩整理:老年代空间由已经分配对象的空间和空闲空间共同组成。新生代垃圾收集将对象由Eden空间挪到老年代空间时,JVM会尝试使用那些空闲的空间来保存这些晋升的对象。

通过GC日志,可以看到回收过程划分成了好几个阶段。虽然主要的并发回收(ConcurentCycle)阶段都使用后台线程进行工作,有些阶段还是会暂停所有的应用线程,并因此引入短暂的停顿。

并发回收由“初始标记”阶段开始,这个阶段会暂停所有的应用程序线程:

89.976:[GC [1 CMs-initial-mark: 702254K(1398144K)]
						772530K(2027264K),0.0830120 secs]
						[Times: user=0.08 sys=0.00,real=0.08 secs]

这个阶段的主要任务是找到堆中所有的垃圾回收根节点对象。从第一组数据中可以看到这个例子中对象占用了老年代空间1398 MB中的702 MB空间。第二组数据显示整个堆的大小为2027 MB,其中772 MB被占用。应用程序线程在这个CMS回收周期中被暂停了0.08秒。

下一个阶段是“标记阶段”,这个阶段中应用程序线程可以持续运行,不会被中断。GC日志中,这个阶段的标识如下:

90.059:[CNS-concurrent-mark-start]
90.887:[CHS-concurrent-mark:0.823/0.828 secs]
				[Times: user=1.11 sys=0.00,real=0.83 secs]

标识阶段耗时0.83秒(以及1.11秒的CPU时间)。由于这个阶段进行的工作仅仅是标记,不会对堆的使用情况产生实质性的改变,所以没有任何相关的数据输出。如果这个阶段还有数据输出,很可能是由于这0.83秒内新生代对象的分配导致了堆的增长,因为应用程序线程还在持续运行着。

然后是“预清理”阶段,这个阶段也是与应用程序线程的运行并发进行的:

90.887:[CMS-concurrent-preclean-start]
90.892:[CNS-concurrent-preclean:0.005/0.005 secs]
[Tlmes: user=0,01 sys=0.00,real=0.01 secs]

接下来的是“重新标记”阶段,这个阶段涵盖了多个操作:

90.892:[CNS-concurrent-abortable-preclean-start]
92.392:[GC 92.393:[ParNew: 629120K->69888K(629120K),0.1289040 secs]
							1331374K->803967K(2027264K),0.1290200 secs]
							[Times: user=0.44 sys=0.01,real=0.12 secs]
94.473:[CNS-concurrent-abortable-preclean: 3.451/3.581 secs]
							[Times: user=5.03 sys=0.03,real=3.58 secs]
							
94.474:[GC[YG occupancy: 466937 K(629120 K)]
			94.474:[Rescan (parallel),0.1850000 secs]
			94.659:[weak refs processing, 0.0000370 secs]
			94.659:[scrub string table,0.0611530 secs]
						[1 CNS-remark:734079K(1398144K)]
						1201017K(2027264K),0.1863430 secs]
			[Times: user=0.60 sys=0.01,real=0.18 secs]

使用可中断预清理阶段是由于标记阶段不是并发的,所有的应用线程进入标记阶段后都会被暂停。如果新生代收集刚刚结束,紧接着就是一个标记阶段的话,应用线程会遭遇2次连续的停顿操作,CMS收集器希望避免这样的情况发生。使用可中断预清理阶段的目的就是希望尽量缩短停顿的长度,避免连续的停顿。

因此,可中断预清理阶段会等到新生代空间占用到50%左右时才开始。理论上,这时离下一次新生代收集还有半程的距离,给了CMS收集器最好的机会避免发生连续停顿。这个例子中,可中断预清理阶段在90.8秒开始,等待常规的新生代收集开始花了1.5秒(根据日志的记录,92.392秒开始)。CMS收集器根据以往的历史记录推算下一次新生代垃圾收集可能持续的时间。'这个例子中,CMS收集器计算出的时长大约是4.2秒。所以2.1秒之后(即94.4秒),CMS收集器停止了预清理阶段(这种行为被称为“放弃”了这次回收,不过这可能是唯一能停止该次回收的方式)。这之后,CMS回收器终于开始了标记阶段的工作执行,标记阶段的回收工作将应用程序线程暂停了0.18秒(在可中断预清理过程中,应用程序线程不会被暂停)。

接下来是另一个并发阶段——清除(sweep)阶段:

94.661:[CNS-concurrent-sweep-start]
95.223:[GC 95.223:[ParNew: 629120K->69888K(629120K),0.1322530 secs]
							999428K->472094K(2027264K),0.1323690 secs]
							[Times: user=0.43 sys=0.00,real=0.13 secs]
95.474:[CNS-concurrent-sweep:0.680/0.813 secs]
						[Times: user=1.45 sys=0.00,real=0.82 secs]

这个阶段耗时0.82秒,回收线程与应用程序线程并发运行。碰巧这次的并发-清除过程被新生代垃圾回收中断了。新生代垃圾回收与清除阶段并没有直接的联系,将这个例子保留在这里是为了说明新生代的垃圾收集与老年代的垃圾收集可以并发进行。从上面的图中可以看到,新生代的状态在并发收集的过程中发生了变化——清除过程中新生代可能发生了多次垃圾收集(至少发生了一次新生代垃圾收集,因为可中断的预清理至少会经历一次新生代垃圾收集)。接下来是并发重置(concurrent reset)阶段

95.474:[CMS-concurrent-reset-start]
95.479:[CMS-concurrent-reset: 0.005/0.005 secs]
	   [Times: user=0.00 sys=0.00,real=0.00 secs]

这是并发运行的最后一个阶段;CMS垃圾回收的周期至此告终,老年代空间中没有被引用的对象被回收。 但是无法从日志中了解到底有多少对象被回收;重置阶段的日志也没有提供更多的信息,最后还有多少堆空间被占用不得而知。为了发掘这些信息,可以尝试从新生代垃圾收集日志中找到一些蛛丝马迹,如下所示:

98.049:[GC 98.049:[ParNew:629120K->69888K(629120K),0.1487040 secs]
			1031326K->504955K(2027264K),0.1488730 secs]

与89.853秒时(即CMS回收周期开始之前)老年代空间的占用情况相比较,那时的空间占用大约是703MB(整个堆的占用为772 MB,其中包含69 MB的Survivor空间占用,因此老年代占用了剩下的703 MB)。到98.049秒,垃圾收集结束,老年代空间占用大约为504 MB,由此可以计算出CMS周期回收了大约199MB的内存。

如果一切顺利,这些就是CMS垃圾回收会经历的周期,以及所有可能出现在CMS垃圾收集日志中的消息。不过,事实并不是这么简单,还需要查看另外三种消息,出现这些日志表明CMS垃圾收集碰到了麻烦。首当其冲的是并发模式失效(concurrent modefailure):

267.006:[GC 267.006:[ParNew: 629120K->629120K(629120K),0.0000200 secs]
		267.006:[CMS267.350:[CMS-concurrent-mark: 2.683/2.804 secs]
		[Times: user=4.81 sys=0.02,real=2.80 secs]
		(concurrent mode failure):
		1378132K->1366755K(1398144K),5.6213320 secs]2007252K->1366755K(2027264K),
		[CMS Perm:57231K->57222K(95548K)],5.6215150 secs]
		[Times: user=5.63 sys=0.00,real=5.62 secs]

新生代发生垃圾回收,同时老年代又没有足够的空间容纳晋升的对象时,CMS垃圾回收就会退化成Full GC。所有的应用线程都会被暂停,老年代中所有的无效对象都被回收,释放空间之后老年代的占用为1366 MB——这次操作导致应用程序线程停顿长达5.6秒。这个操作是单线程的,这就是为什么它耗时如此之长的原因之一(这也是为什么发生并发模式失效比堆的增长更加恶劣的原因之一)。

第二个问题是老年代有足够的空间可以容纳晋升的对象,但是由于空闲空间的碎片化,导致晋升失败:

6043.903:[GC 6043.903:
		[ParNew (promotion failed): 614254K->629120K(629120K),0.1619839 secs]
		6044.217:[CMS:1342523K->1336533K(2027264K),30.7884210 secs]
		2004251K->1336533K(1398144K),
		[CNS Perm; 57231K->57231K(95548K)],28.1361340 secs]
		[Times:user=28.13 sys=0.38,real=28.13 secs]	

在这个例子中,CMS启动了新生代垃圾收集,判断老年代似乎有足够的空闲空间可以容纳所有的晋升对象(否则,CMS收集器会报告发生并发模式失效)。这个假设最终被证明是错误的:由于老年代空间的碎片化(或者,不太贴切地说,由于晋升实际要占用的内存超过了CMS收集器的判断),CMS收集器无法晋升这些对象。

因此,CMS收集器在新生代垃圾收集过程中(所有的应用线程都被暂停时),对整个老年代空间进行了整理和压缩。好消息是,随着堆的压缩,碎片化问题解决了(至少在短期内不是问题了)。不过随之而来的是长达28秒的冗长的停顿时间。由于需要对整个堆进行整理,这个时间甚至比CMS收集器遭遇并发模式失效的时间还长的多,因为发生并发模式失效时,CMS收集器只需要回收堆内无用的对象。这时的堆就像刚由Throughput收集器做完Full GC一样(如图6-2);新生代空间完全空闲,老年代空间也已经整理过。

最终,CMS收集的日志中可能只有一条FullGC的记录,不含任何常规并发垃圾回收的日志。

279.803:[Full GC 279.803:
			[CMS: 88569K->68870K(1398144K),0.6714090 secs]558070K->68870K(2027264K),
			[CHS Pern:81919K->77654K(81920K)],
			0.6716570 secs]

永久代空间用尽,需要回收时,就会发生这样的状况;应注意到,CMS收集后永久代空间大小减小了。Java 8中,如果元空间需要调整,也会发生同样的情况。默认情况下,CMS收集器不会对永久代(或元空间)进行收集,因此,它一旦被用尽,就需要进行FulI GC,所有没有被引用的类都会被回收。

快速小结

  1. CMS垃圾回收有多个操作,但是期望的操作是Minor GC和并发回收(concurrent cycle)。
  2. CMS收集过程中的并发模式失效以及晋升失败的代价都非常昂贵;我们应该尽量调优CMS收集器以避免发生这些情况。
  3. 默认情况下CMS收集器不会对永久代进行垃圾回收。

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

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

相关文章

Python配置MySQL数据库使用

创建配置文件 config.ini [MySQL] host 172.xxx.xxx.xxx port 3306 user root password ****** db bgp_routing charset utf8创建读取配置文件 readConfig.py import configparser from pathlib import Pathclass ReadConfig():def __init__(self):configDir Path.cwd…

【学习日记2023.6.6】之 Linux环境下部署Java项目

文章目录 5. 项目部署5.1 手动部署项目5.2 基于Shell脚本自动部署5.2.1 介绍5.2.2 推送代码到远程5.2.3 Git操作5.2.4 Maven安装5.2.5 Shell脚本准备5.2.6 Linux权限5.2.7 授权并执行脚本5.2.8 设置静态IP 5. 项目部署 开发的项目绝大部分情况下都需要部署在Linux系统中。下面通…

springboot+vue多维的知识分类管理系统

随着国内市场经济这几十年来的蓬勃发展,突然遇到了从国外传入国内的互联网技术,互联网产业从开始的群众不信任,到现在的离不开,中间经历了很多挫折。本次开发的多维分类的知识管理系统有管理员和用户两个角色。管理员可以管理用户…

Dozzle-解决通过命令方式查看Docker 日志的神器

对于程序员们来说,Docker 一定是不陌生了。Docker 为我们的工作带来的巨大的便利,你可以使用它快速部署和扩展应用程序,并保证隔离性和可移植性,使应用程序在容器内独立运行,而且可以轻松地在不同的主机和操作系统上移…

bpmn是什么?bpmn.js的简单使用

文章目录 一、bpmn.js是什么?二、使用步骤1.引入bpmn2.使用bpmn3.引入bpmn-左侧工具栏4.引入bpmn-左侧工具栏5.引入bpmn数据导出6.数据导出为svg格式7.监听modeler并绑定事件7.监听element点击……8.自定义左侧工具栏图标9.自定义左侧工具栏完整效果 总结 一、bpmn.…

推动体系建设 助推融合发展|2023 开放原子全球开源峰会软件物料清单(SBOM)分论坛即将启幕

软件物料清单对于普通人而言可能很陌生,而对于从业者而言,软件物料清单是以 “开源” 为核心,通过有效识别和记录软件组成成分及相互依赖关系,保障软件全生命周期各环节要素的可控制、可预测、可管理。 由开放原子开源基金会主办…

云原生Docker网络管理和数据卷

Docker网络 Docker 网络实现原理 Docker使用Linux桥接,在宿主机虚拟一个Docker容器网桥(docker0), Docker启动一个容器时会根据Docker网桥的网段分配给容器一个IP地址,称为Container-IP, 同时Docker网桥是每个容器的默认网关。 …

案例26:基于Springboot校园社团管理系统开题报告

博主介绍:✌全网粉丝30W,csdn特邀作者、博客专家、CSDN新星计划导师、java领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ 🍅文末获取源码联系🍅 👇🏻 精彩专…

那些你可能遇到的 Linux 命令?什么,你还不知道?赶紧收藏?完善中!

文章目录 一. Linux 进程1. 通过进程名查找进程号1.1 ps aux & ps -ef:diff1.2 ps aux & ps -aux:什么?它们不一样?1.3 grep & awk:取出进程号、取出进程号并 Kill 2. 通过进程号查看进程信息:…

视频理解学习笔记(四)

视频理解学习笔记(四) 3D CNNC3DI3DNon-local算子 (Self-attention替换掉LSTM)R (2 1) DSlowFast Video TransformerTimeSformer 总结Reference 3D CNN 双流的缺点:光流抽取太慢——tvl one算法,0.06s抽取…

什么是浅拷贝和深拷贝

javascript 中有不同的方法来复制对象,那么我们怎样才能正确地复制一个对象呢?,本文来介绍一下浅拷贝和深拷贝。 一、什么是浅拷贝(Shallow Copy) 浅拷贝是创建一个新对象,这个对象有着原始对象属性值的一份精确拷贝。如果属性是基本类型,拷…

遗传算法在数学建模中的应用及MATLAB实现

2023年9月数学建模国赛期间提供ABCDE题思路加Matlab代码,专栏链接(赛前一个月恢复源码199,欢迎大家订阅):http://t.csdn.cn/Um9Zd 目录 遗传算法基本概念 遗传算法原理 MATLAB实现 1. 使用ga求解遗传算法问题 数学建模案例:旅行商问题(TSP&#xf…

操作系统原理 —— 内存连续分配管理方式(二十)

在之前的章节中,我们到了内存管理,其中有一个很重要的功能,就是对操作系统中的内存进行分配和回收。 那如何对操作系统的内存进行分配呢? 整体上可以分为两种方式:连续分配管理方式、非连续分配管理方式。 这里提到的…

【vue3】08-vue的组件化开发-插槽(Slots)的完全指南

Vue插槽(Slots)的完全指南 插槽的作用插槽的基本使用具名插槽作用域插槽(难点) 插槽的作用 在开发中,我们会经常封装一个个可复用的组件: 前面我们会通过props传递给组件一些数据,让组件来进行展示;但是为…

【CVPR2023】TPS详解:联合令牌剪枝与压缩以实现视觉变形器更积极的压缩

【CVPR2023】TPS详解:联合令牌剪枝与压缩以实现视觉变形器更积极的压缩 0. 引言1. 为什么要使用TPS?2. TPS介绍3. TPS 详解3.1 重要性计算3.2 令牌压缩3.2.1 匹配3.2.2 融合 4. 简化版理解5. 总结 0. 引言 虽然 Vision Transformers (ViTs&a…

小文智能宣布接入ChatGPT,智能化客户服务,开创全新用户体验

小文智能是一家致力于用AI技术解放劳动力的公司,最近我们接入了ChatGPT技术,深度探索AI在智能对话机器人领域应用的更多可能,这将为我们的客户带来更为优质的人机对话服务和全新的用户体验。 ChatGPT是一种基于人工智能的自然语言处理技术&a…

案例31:基于Springboot企业员工薪酬关系系统开题报告设计

博主介绍:✌全网粉丝30W,csdn特邀作者、博客专家、CSDN新星计划导师、java领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ 🍅文末获取源码联系🍅 👇🏻 精彩专…

逍遥自在学C语言 | break-循环的中断与跳转

前言 在C语言中,break语句是一种控制流语句,它用于终止当前所在的循环结构(for、while、do-while)或者switch语句,从而跳出循环或者结束switch语句的执行。 一、人物简介 第一位闪亮登场,有请今后会一直…

ML算法——梯度下降随笔【机器学习】

文章目录 11、梯度下降 11、梯度下降 梯度下降如何帮助参数优化? 梯度下降是一种用于参数优化的常见方法。它的基本思想是通过迭代地更新参数,以减小损失函数|代价函数的值,从而找到一个最优解。 梯度方向:→|向右|正向 ←|向左|反…

PostGIS(1):PostGIS概述

作为对象关系型数据库PostGreSQL的拓展模块,PostGIS可用于存储GIS数据,并提供了对基于GiST的R树索引支持、以及面向GIS对象的分析和处理相关的函数。 以下是PostGIS官网对其特征的介绍, (1) 先看一下百度对PostGIS的介…