- 代码性能的终极信条:
- 如果可以,严格控制对象的生命周期:要它三更死,绝不到五更。要它早上八点活,绝不提前半秒。
- 场景:
- 问题:实例化该对象的时候,这几个字段就会初始化(初始化是为了避免null值),但是这些初始值并没有意义,反而占用额外的内存,这些内存在这些对象被赋予有意义的值以后,就会成为gc的回收对象,从而会给gc带来额外的压力(从而造成卡顿)。增加的gc频率取决于这些内存在应用中所占的比例,以及虚拟机的配置情况。
- 解决办法:懒初始化。如,在set方法中增加为空判断和进行初始化。
- 通常java中对象的出生时间明确,回收时间(gc)不明确
- 场景:交易大师登录线程:
- 说明:图3是Netty的一个类,是根据图2中的new NioEventLoopGroup(2),在ctrl+鼠标左键层层进去得到的,通过图3可以看到,如果图2没有参数2,默认的线程数量将会是应用所在主机的线程数量的2倍,例如4核主机就是8个线程
- 问题:登录访问后台只是为了获取token,线程的使命既然在此,那么完成使命后就应该销毁线程。但是netty并不会销毁这些线程。
- 解决方案:用httpClient代替netty连接,不再单独创建线程。
- 补充说明: 实际上此处是一个非常不合理的设计,参见图1,先是采用线程池,利用线程池里的线程又来创建netty线程。虽然线程池里的线程受线程池管理,但是创建的netty线程却不受限制。极端情况下,如果客户端反复进行登录,而不退出应用,那么在客户端会创建大量无意义的线程,导致线客户端程激增。另外netty线程会连接上服务器,对netty服务器端可能会造成不必要的压力。
- 场景:
- 如果可以,严格控制资源利用率:
- 我能吃2碗饭,我就不能饿着自己,但我也不应该引发浪费。
- 场景:参见上面的netty登录线程,登录只需要1个线程就够了,而且是用完立即销毁的线程,那么netty这里创建2个线程就是浪费。
- 场景:线程池的coreSize最佳值设定。当应用(尤其是服务器)处于最常用的状态时,线程池的线程使用情况往往代表了线程的coreSize。
- 我能吃2碗饭,我就不能饿着自己,但我也不应该引发浪费。
- 日志的输出频率不要太高。朝生夕死的大对象要慎重创建,朝生夕死的小对象也不该频繁创建。譬如,在一个高频循环中插入日志,这会导致内存占用快速增长,gc频率就会比较高,表现为卡顿。java中台曾经有一个静态行情的判空方法,如果在这个方法中插入一条日志,那么整个应用启动日志会增长7、8倍(几十兆变成几百兆),像这种高频方法如果插入一条简单的日志,对应用的危害都是比较大的。
- 补充:线程利用率查看办法:
- 线程池利用率实时查看:还是以交易大师登录为例:打开应用,用jvisualvm连接上该应用,打开jvisualvm的线程界面,进行反复登录而不退出应用的操作,就会发现多出许多线程组: 从上图可知,每两个nioEvnetLoopGroup为一组,我进行了5次登录操作。还可以看到登录除了会增加这两个线程,还会有pool-xx-thread-1这个线程池增加线程,可能是图1的exe线程池增加的线程。过一段时间不再操作后还会发现,每组nioEventLoopGroup线程中有1个处于驻留状态,有一个处于运行状态,而pool-xx-thread-1的线程基本上都被清理了(线程池管理)。
- 如果可以,严格控制对象的生命周期:要它三更死,绝不到五更。要它早上八点活,绝不提前半秒。