简单内存复用与细节优化

一、内存优化细节

如图可以看出有内存抖动,解决办法是内存缓存。

使用内存缓存的时候需要注意的点就是当一个实例不再使用的时候,也应该去释放这个实例,不能让这个实例越积累越多,从而导致内存溢出。所以内存溢出的概念就是,当一个实例被创建之后,后续过程中不可能用到这个实例的时候,但是我们把这份实例给存储起来了,导致GC无法回收这部分实例,也就导致内存不断上涨,这就是内存溢出。内存溢出是一个不断上涨的曲线,并且GC回收后该曲线也无法下降。

但是这个曲线并不是代表内存溢出,而是生产者的速度大于消费者的速度,此时我们缓冲区中的数据会不断上升,达到无限状态,将内存占用完。

register方法中就很有可能造成内存抖动,IoTask创建之后没有进行复用,每次都会去创建一个,创建之后就没有了,用掉之后就丢掉了,

 

虽然在执行完成之后,如果返回true,会重新加载到队列当中,此时有一个内存复用的优化,所以对性能的损耗不会太多。但是应该避免出现这样的情况,因为有多个线程调用,此外这个方法可能返回的是一个false,没有返回true。所以这里需要优化。

register方法传入了一个SocketChannel、一个类型、一个callback,callback用于真正的任务执行的时候任务的调度,这个方法可以通过IoStealingSelectorProvider来看到。在这个方法中可以看到调用了callback的run方法,

 

由于callback的run方法返回的是void

 

这也就导致了provider无法返回true直接进行注册的原因,也就是说无法直到当前这个程序执行这一遍数据之后,它后续还有没有数据。

 

它只能通过后续重新注册的方法,也就是再次进行register的方法,来进行任务的注册

而不能直接通过做完任务的方法直接进行注册。所以,应该将IoTask和Io'Provider联系起来

 

IoProvider中的HandlerCallback,本质来说它就是一个执行任务的地方,IoTask也是,可以将这两者进行一个联合。

IoTask增加一个返回boolean值的onProcessIo的方法,表明是否处于Io执行的状态。

 

在IoProvider的callback中,之前onProviderIo返回的是void。

在run方法中,首先清空attach,如果onProviderIo(attach)返回true,说明还有数据需要处理,再重新注册。

二、总结

 

三、内存抖动优化

Register是我们认为会产生内存抖动的地方,为什么会这么认为呢?因为每次调用register,都会执行new Task创建一个新的实例的方法,而这个实例在后面的流程当中,用完了也就丢掉了,并没有把它持续地循环利用。

在读取和写入的时候都会调用这个实例,而读取和写入的调度,其实是在整个生命周期当中,当有数据发生一个读取或者写入之后,它都会进行一个判断,而整个业务流程的Dispatcher都会对它进行注册。整个调度过程由selector的线程来完成,这个线程就是Loop Thread。这个Loop Thread本身就在循环,本身会产生非常多的读取或写入的注册事件,这个注册事件会导致register频繁地被触发,频繁地register,也就会导致频繁地创建Task,而这N个Task随后用完就丢,内存不断上涨,涨到一定程度被GC回收,内存回收会引起内存波浪型地抖动。锯齿的形状从侧面反映了GC的频繁度,GC越频繁,就导致了其它线程不工作的状态,也就降低了性能。

 

优化方式:

1、

之前processTask这个地方永远返回的是false,此时将其做了一个具体的实现,根据业务的实现返回true或者false。如果返回true,直接将其添加到队列中去。

这里主要完成了两个问题,一个是task的复用,第二个是减少了register的调度,这就是我们的优化点。

 

2、register对其进行了更改,之前是需要传入通道、类型信息和callback,但是经过排查和分析之后,发现task其实是可以和callback合并在一起的。既然可以合并在一起,那么传入的时候就可以直接传入task,从而降低new task这个过程。而task内部则包括了channel和类型,callback是由Task的直接子类来完成。

 

3、

Task的直接子类在Adapter的实现中来完成,在adapter中本身就有两个基础的变量,这两个变量就是我们的callback,分别代表了输入和输出,callback是IoTask的子类,也就是说当前的IoTask是随着当的adapter实例一起创建的,当adapter的实例在构造方法创建的时候,在内部就完成了对应task的实现。简单来说,一个连接只可能创建两个task,并且这两个连接没有关闭,那么这两个连接将会永远去复用这个task,不会再创建新的task的实例。这里就保证了task上涨之后不再使用的问题,从而减少了IoTask引起的内存抖动。当然,在整个代码中不是只有这一个地方会引起内存抖动,还有其它非常多的地方,比如IoArgs。IoArgs已经固化到了Selector当中,这个点其实是可以忽略的。第二点是Frame,帧的构建本身就是可能会引起内存抖动的地方,以及我们的packet,这个地方是内存抖动的大头,这些都是可以进行优化的地方。

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章