00:00
前面我们使用解密特做了一个压力测试,那通过我们对某个接口的压力测试,我们可以得到我们这个接口的一些性能报告,那我们通过衡量这些性能报告,我们就能知道我们这个接口有没有符合我们的这个性能要求,如果不符合性能要求,我们就要对接口进行一些优化,那如何进行优化,我们接下来就来说一下,首先这个优化呢,我们得考虑多方面,第一方面我们这个接口呢,可能要操作数据库,那数据库快不快,慢不慢,包括我们的SQ语句性能高不高,这些都是我们要考虑的问题。第二个我们这一块的应用程序,我们有没有更优化的写法啊,我们的编码是不是存在哪些问题,包括我们要过这些中间件,特别是我们的,比如我们这个首页请求,我这个首页请求从们发请求出去,我们先要通过NX,然后呢,NX将请求又转交给我们微服务后台集群的网关,网关又转给我们微服务,微服务里边呢又是Tom k Tom。
01:00
调用我们程序来进行处理,通过这一段逻辑返回以后,我们过了好多的中间件,从我们的NX到网关到tomcat,那这些中间件呢,都是影响我们性能的原因,所以我们也得优化的时候来考虑他们,包括我们的一些网络IO,比如我们当前网速慢,我们服务器压力大,或者带宽只有一兆,那现在1万个并发请求,那就算每个请求发送个1KB的数据,那都发了十兆的数据,所以我们这个带宽太拥挤,这也是影响我们接口的整个吞吐量性能的原因,包括呢,我们这个操作系统,我们选用的操作系统不同,可能它们里边对内核的一些处理等等都有一些区别,所以呢,我们要做性能优化,我们就要考虑多方面,但呢,不管考虑哪些方面,我们大体的优化呢,我们先得想好,我们这个应用是属于CPU密集型还是IO密集型。
02:00
CPU密集型那指的就是我们当前应用是浪费CPU呢,还是我们这个浪费我们所说的IO,这IO呢,不论是包含我们这个网络IO,通过网络间传输数据,还是我们要磁盘读写,这也算是我们磁盘的IO,包括我们从数据库里边读数据,这也是相关磁盘的这些IO,包括我们从red里边缓存读数据,这也是网络,包括red要进行IO,那我们CPU密集型应用,那就是大量的计算,比如我们查到了一堆数据,我们要计算、排序、过滤、整合等等这一堆,那这是计算的,那是CPU密集型的,包括呢,我们如果通过后台性能监控,我们可以看到CPU呢,一直是占用率百分之七八十,一直100%等等等等,那这就是CPU密集型应用,那还有一些IO密集型的,我发现呢,CPU每天都很闲,CPU经常占用百分之三十四十啊,顶多到六十七十,但是呢。
03:01
我们这个IO占用量非常大,我们这个内存也挤爆了,包括我们的这个磁盘也在疯狂的读写数据,那说明我们这个应用呢,就是IO密集型,包括我们这个网络的流量也非常巨大,每秒呢要出入很多的这个字节数据,所以呢这些都属于IO密集型。那针对于这些应用呢,我们就得针对性的进行处理,如果只是CPU密集性的应用,那我们升级服务器加上CPU,那其实也就解决问题了,包括呢,我们如果CU一个能力处理不够了,我们来上十台服务器,我们分担处理,我们来做并行任务,这其实也是没问题的,但如果是IO密集型,那我们除了比如换固态硬盘加内存条,使用各种缓存技术,我们提高网卡的传输效率等等等等外。我们单加CPU可能就没什么作用了,所以呢,包括我们来看我们以前的这个接口优化,我们发现呢,我们来优化我们这个鼓励mail,我们当时呢,吞吐量130多,然后呢,我把我们的这个古力妙的我们这个product服务,我把内存调大了,它的吞量呢也达到了140多,每秒只多了十来个,其实通对吞吐量的提升倒没有非常大,所以呢,这种情况下,那有可能其实就是我们写的这些接口里边,我们这些方法呢,其实都需要做一些优化,那到底如何优化,那我们而且还要。
04:30
看一些监控指标,除了我们性能测试的这些报告外,那我们最好能看一些监控指标,比如我们当前应用接口正在运行期间们在压力测试期间,我们一直在压这个查询所有分类的接口,我们发现它CPU的占用率是多少的,我们这段时间持续占用率多少,包括内存的占用率,包括呢,如果这个们当前微服务的整个线程数有多少等等等等,我们全部能监控到的话,我们来做一个合理优化和分析,那其实才是一个真正的优化,所以这些性能的监控和分析。
05:06
那我们接下来呢就得说一下,那这个性能监控分析呢,我们主要是来监控我们GVM的整个工作,包括它里边的一些内存情况,线程情况,我们都得监控,那如何监控以及分析和调优,那接下来呢,我就着重说一下Java的内存模型和它里边的堆垃圾回收机制等这些问题,当然这一块呢,大家如果知道的同学就可以来跳过了,不知道的同学呢,我先带大家大致的过一遍,想要知道更详细的内容,大家需要参照我们周阳老师来讲解的GVM相关的内容,大家可以去鼓励学院来寻找相关的课程。好,我先来说一下Java的这个内存模型,这个内存模型呢,我们来开发Java应用,我们都知道,我们首先写的java.java这个源代码文件会被编译成点class文件,这class文件呢,会被我们GVM的类装载器来装载到我们GVM里边,这所有的数据都在我们运行时数据区,所以呢,我们优化的。
06:07
的大部分都在运行式数据区,当然如果大家很厉害的话,可以来写我们GVM的执行引擎了,那你可以来优化一下它的这个代码的执行引擎,那当我们所有的数据都在运行式数据区里以后,包括我们的这些代码方法这些等等都进来了以后,那就由我们GVM的执行引擎来负责执行。执行方法呢,它就会在虚拟站基站里边来进行方法的一次调用,入站出站等等这些操作来知道一下就行,然后呢,包括如果要调一些本地接口方法,这些本地方法呢,指的都是我们操作系统暴露的这些本地接口以及本地方法库等等这些,想要调用这些呢,它就得调一些本地方法,包括呢,我们程序调到哪了?每一个程序都走到哪一行了,走到哪了,这都有一个程序计数器,我都在这大致的给大家形象的比喻一下,那这块的东西呢?画的这个图还说了这是线程隔离的数据区,相当于每一个线程我们在调用的。
07:07
的时候,他自己呢,都会有这些内容,当前线程调用到哪了,当前调到哪个方法了,包括当前调的是本地接口的哪个方法等等,他这一块呢,都会说都会有记录。每一个线程呢,都是隔离的,自己保存自己的,然后呢,大家共享的就是方法区以及堆,我们方法运行的所需的一些对象数据等等,我们都在这儿保存的,那么重要的呢,就是这个堆,所以接下来我们就重要的给大家再来说一下这个堆,那这一块呢,大致的过一下就行,知道我们优化更多的是在我们堆这一块,那说起堆呢,那其实这就得引申出一个东西,我们都知道我们这个章啊。的这个GVM,它是由我们这些C语言编写的,因为我们以前的这些C语言大神们知道,大家在用C语言的时候呢,经常要开辟空间,然后还要释放内存等等等等,这些操作呢,非常麻烦,而且C语言的语法也比较不好用,所以呢他们基于C在封装写了一个执行引擎,然后呢去来翻译写的这些Java代码,所以呢,我们写Java的时候,我们不去关心内存,而是由底层的GVM,也由这些C语言大神们开发的这些来去关心内存,那内存的开辟以及释放都是由我们这些底层进行掌控的,也就是GVM,但如何掌控的,我们就得说一下我们这一块的整个区,那前面几个呢,还是我们之前上面的图看到的本地方法站,虚拟基站,那就是方法自己调用的时候的一些,包括程序计数器,而我们真正优化的所有内容核心都在这一块,那这一块呢,首先有一个堆堆呢,就是。
08:52
对我们所有对象的创建,对象实例的创建,我们数组的空间的分配,我们都在我们这个堆里边,所以我们整个的调优内存这一块我们都是在调,对那扎VA8以后呢,还有一个原数据区,这个原数据区呢,是直接操作我们物理内存的,还有我们代码编译,我们及时编译,我们正在运行期间,我们哪个代码没编译呢?我们编译期间的所有的代码缓存我们都在这一块,当然这一块的调节也是会有的,当然我们更多的是来看堆,那说起堆了,我们就得说一下我们堆里边的这些分区,当然这里边这分区就是他们设计时的一些思想,首先一句话,我们运行期间我们所有对象的实例创建,以及我们内存分配,我们都放在了堆里边,所以呢,我们要管理好这一块的内容,而堆呢,分为大体分为两个区域,一个叫新生代,一个叫老年代,新生代里边呢,又分了三。
09:52
三块区域分别叫一顿区,就是我们的称为伊甸园区域,还有S0SURVIVOR,我们幸存者,幸存者呢有两个区域,那他们这些分区有什么意义,那我们就得说一下在我们GVM底层的垃圾回收机制GC,那垃圾回收呢,就是在堆区来进行垃圾回收,也就是我们没用的对象,他及时的把它们回收掉,给我们腾出空间,让我们继续来创建新对象,来继续使用内存,而接下来它的整个流程就是这样子的,来看一下看之前呢,首先我们来说一下,我们整张图呢,是基于我们这个JAVA8的这个GVM内存模型的。
10:31
那这样八以前呢,还有一个永久带,但是已经移除了,取而代之的是我们这个称为原空间,但是这个原空间呢,我们说这直接来操作我们这个物理内存的,而垃圾回收存在于我们这个新生代,也就是这三个区域和老年代,我们说新生代换了三个区域,伊甸园区,SURVIVOR0和SURVIVAL1,那它们都是什么作用,我们来说一下,我这呢有一个我们整个内存分配的这个流程图,包括呢,我们来先来看一下这张图,这张图呢从前边划分。
11:06
这一块呢,是我们说的新生代,黄颜色这一块呢,是我们老年代,那大家光听这个名字,新生代那就是新出生的东西都在这一块,那老年代那就是存活了很久的都在这一块,那接下来这一块的东西都是怎么区分呢?来看这张图,比如我们新创建的一些对象,那进来呢,要分配内存,他呢先会去我们这个新生代里边进行分配,那具体呢,就是他先要看我们这个伊甸园区,那这个新生代里边两大区,一个是伊甸园区,那就是刚过来的,还有一个叫幸存者区,那他先去伊甸园区呢,判断我们这个伊甸园区内存空间够不够,如果够的话呢,那就直接给他分配内存,那就够了,那如果不够,那此时呢就要进行一次GCGC指的就是我们垃圾回收,这次呢进行一次叫样GC,我们也叫minor g c,这次GC呢,主要是来清理我们这个新。
12:06
灯带的空间怎么清理呢?比如我们这个伊甸园区里边,之前存了呢十个对象,其中呢,有一个对象现在还在用,其他九个呢都没用了,那我们就把这九个呢踢出来,然后这一个对象呢,我们还会把它放到幸存者区里边,那么一会呢再来看幸存者区流程。所以说具体就是我们新创建来的对象,如果在新生代伊甸园区能放得下,那就放,放不下GC一次,看能不能放得下,放得下就放,如果我们这一次小小的GC,这个MGC是一次非常小的GC也非常快,这个小小的GC如果发生了以后,他能放得下了,那最好不过,如果他还放不下了,那呢,我们就认为这是一个大对象,我们尝试呢,把它放在老年代,那一句话就是老年贷是在我们这个新生代没法处理的情况下,我们才进老年贷的,那好,那接下来呢,也要把对象给老年代里边放了,不管放到哪一个内存区域里边,肯定都得判断放得下放不下,所以如果说我们这个对象刚创建来。
13:14
新生代放不下,小小GC了一次也放不下,那接下来他就来到老年代,如果老年代能放下,那最好不过直接分配内存,如果老年袋还是放不下,那接下来我们就要进行一次负GC,就是我们说的全面GC,这就是一次大屠杀,整个大屠杀呢,把所有老年代里边存的数据,新生代里边的数据全部呢都看一看,哪些没用了,全给他踢出去,那踢出去以后呢,再来看,如果放得下,那就放下了,如果老年代还放不下,那就会报内存溢出异常,我们这个内存不够了,就是oom out of memory,所以呢,这次four GC清理掉的对象也能腾出一点空间,那这就是我们前续的这一块流程。
14:01
任何新对象的申请,我们先来看我们这个伊甸园区,放得下放不下。如果放下了就放,放不下了,小小的TC一次,把我们年轻代,也就是新生代我们来清理一下。如果清理一下呢,放得下了那就放下,放不下了,再来看老年袋,如果老年袋能放下了放,放不下了,再做一次大GC,然后能放下了放,放不下了就报异常,因为两个带呢都尝试,实在放不下了,那就没办法了。这是我们前续的这一块流程,而大家一定要注意,这个four g c非常慢,如果说我们这个米G c100次才花费一秒时间的话,那four g c不到十次就得要花费一秒时间。所以呢,这是一个性能慢十倍左右的这个GC,所以我们说后来优化监控的时候,我们一定要注意,一定要避免我们应用经常性发生负GC的问题,那这一块说完,那接下来呢,我们说右半边的这个逻辑,我们刚才说了小小GC一次呢,会把我们这个伊甸园区的存活的的这些对象我们搬家放到幸存者区。
15:13
因为如果都能搬过来的话,伊甸园区就会拥有更多的内存,所以呢,我们这次样机C他就会把这些旧对象,我们把那些没用的踢出来,把这些存活了的看一下能不能放到幸存者区,如果能放到幸存者区,它就会放到幸存者区,而且幸存者区呢,它有两个区,From和to。这两个呢是来回交换,来回交换目的就是总要腾出一个大白片的空间,但这个细节呢,大家可以参照我们周阳老师讲的GVM以及GC算法之类的,也就是说我们这个在这个样机C发生了以后,把伊甸园区的幸存者就会搬家到幸存者区。而且呢,如果幸存者区的这些对象存活超过这个阈值,什么叫超过浴池阈值了,就是呢,我们小屠杀了十几次,他都还活着,那说明他的生命力顽强,每一次小屠杀呢,我们认为的年龄可以长一岁,好,他现在15岁了,成年人了。
16:14
能把它搬到老年区,也就是我们的老年代,这就是我们说的对象生存超过这个阈值,超过阈值了呢,就会把这些又搬家到老年代,所以老年贷里边呢,存的总是那些生命力持久的对象和我们那些大对象,这我们说的整个这个GC的流程,而GC这一块呢,我们刚才又有一个小细节,就是这一块的分支细节。我们说这个伊甸园区存活的对象,他呢会放幸存者区,如果幸存者区能放下就放里边了,如果放不下,那就把这些存活的对就会直接搬家放到我们这个老年袋里边,那这呢也是一个流程,也就是我一次小的米诺GC总是要把我们伊甸园区给清理干净,有幸存者能到幸存者区放,幸存者区不能到了再放到我们的老年袋就行了。
17:09
那这次GC的这个流程呢,大家主要要知道我们这两个GC的触发时机,一个是样GC,我们伊甸园区的内存不够了,第二个呢是four g c,连我们老年代的这个内存都不够了,而且呢,Four g c要比样GC慢很多。所以呢,我们在性能优化期间,我们要监控我们这个应用,在压力测试期间,我们要时刻关注这两个区的动态变化,并进行一些相应的调优才行。这是我们说的Java内存模型,以及特别重要的我们这个垃圾回收,也就说我们这个堆这一块的整个垃圾回收机制,当我们说的垃圾回收,回收的就是这个,对,与原数据区无关,与我们代码缓存区无关,我们所有新对象数组的创立都在这儿。我们回收的是这。
我来说两句