记一次OOM问题排查以及引出的GC优化
一)介绍
我们有一个服务,主要是给一些商家或者供应商查看售卖的票信息,例如报表下载和一些售票的查询等等。
因为平时用的人比较少,而且我们这边可以配置自动扩容的阈值,所以机器配置是 2C8G,两个机房分别 3 台。
项目很少发版本,最近一次的发版本还是前一个月。
我们有一个服务,主要是给一些商家或者供应商查看售卖的票信息,例如报表下载和一些售票的查询等等。
因为平时用的人比较少,而且我们这边可以配置自动扩容的阈值,所以机器配置是 2C8G,两个机房分别 3 台。
项目很少发版本,最近一次的发版本还是前一个月。
在多线程编程的环境中,一个变量是可以被多个线程访问并修改的,如果想让一个线程,在不影响到其他线程的情况下,修改此变量,那么就需要将该变量改成自己私有的,这就是 ThreadLocal 的作用了。
Threadlocal 可以将一个变量作为自己的私有变量,可以在本线程内随意修改并且不影响到其他线程,如下 Demo:
可以看到 t1 打印出 Thread1,t2 打印出 Thread2,两个线程的 ThreadLocal 都不受影响。
虽然有那么点标题党,但是确实让我损失了一杯星巴克,耽误了测试小姐姐的时间~~
事情的起因是这样的,我们有一个接口是对外修改状态用的,例如状态有1,10,20,30,40,50 等等,状态的流转在业务上来说只允许操作一次,不然会导致一些重复的事件触发。
所以为了防止并发修改导致的系统问题,我们在修改数据库的时候,做了一个 CAS 判断
1 | update tbl set status = ? where status = ? and order_id = ? |
责任链模式是设计模式的一种,可以为调用的对象进行一个链式处理,这种模式在 Java 的一些第三方库中经常见到。
而在业务开发中,这种需求也是很常见的,如果用好这个设计模式,对于代码的扩展性和可维护性都是非常有帮助的,例如常见的下单流程,就可以用责任链模式处理。
而在第三方库中,像 tomcat 的过滤器就是使用责任链模式进行处理
在 tomcat 中,过滤器的实现就完全是责任链模式的使用了
在上一篇的文章中有提到过 CountDownLatch ,其实 CyclicBarrier 也有异曲同工之妙,不过 CyclicBarrier 是等到所有的线程都到达一个点以后,然后再一起执行
有点像小时候一起去春游,必须等到所有的同学都到了学校,才能一起去坐车,不然就会一直等待。
CyclicBarrier 的构造函数有两个,分别如下:
1 | public CyclicBarrier(int parties) { |
CountDownLatch 只有一个构造函数:CountDownLatch(int count)
其中 count 表示该信号量的数量,其中具体的实现类是 Sync,而 Sycn 又是继承自 AQS,实现了几个 AQS 的方法
在生产环境中可以用于分治思想,讲一些复杂的处理分成一些子任务,等所有处理任务处理完毕以后,主线程才会执行
还可以用于一些任务的流程检查,例如只有所有的检查都完毕以后,主线程才可以获取数据然后执行
如果想子线程想使用父线程的 ThreadLocal,那么父线程中的 inheritableThreadLocals 有值,这样子线程中的 init 方法就会自动的将父线程的 inheritableThreadLocals 设置为 ThreadLocal
但是因为子线程是在 init 方法中进行赋值的,所以如果子线程是由线程池创建的,那么该方法就又可能会失效,当线程池刚初始化完毕的时候,此时线程池中的还没有线程,当调用 execute
方法,此时就会 new 一个线程,那么这时候子线程是可以读取到父线程中 inheritableThreadLocals 的值
但是线程池中的线程是可以被复用的,所以后续如果线程不再创建的时候,那么子线程便不能再次获取父线程中的 inheritableThreadLocals,也就无法再将 ThreadLocal 进行父子线程的传递
SPI 全称是 Service Provider Interface,是 JDK1.5 新增的一个功能,允许不同的服务提供者去实现某个规定的接口,而且将具体的实现完全提供给使用方,允许使用方按需加载服务提供方的一些功能。
提到 SPI,就不得不提下 API,以 dubbo 为例,服务提供方对外提供一系列 API,而使用方是不用关心服务提供方是如何实现具体的业务逻辑,只需要通过 RPC 调用远程服务即可。
这样的好处就是 client 端不用关心服务端的具体逻辑,方便服务的水平扩展以及解耦。
JVM 规范中写道:在加载类的时候,分为如下几个大类:
每一步在 JVM 中都规定了具体的几个小节,但是今天本文的重点在于链接阶段,JVM 对一些静态变量做的一些优化,因此对于这里面的每一步具体是做什么的不展开讨论了。
在 Java 中,多线程的核心实现类是 ThreadPoolExecutor,该类提供了多线程的几个参数,用于开发人员自定义自己的线程池。
1 | public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize,long keepAliveTime, |
线程池一共有 7 个参数,其中跟本次相关的有三个,分别是 corePoolSize、maximumPoolSize、keepAliveTime,这三个参数代表的意思如下: