压测调优之遇到的proxool问题

来源:互联网 发布:python全局变量的使用 编辑:程序博客网 时间:2024/04/29 13:15

T4CPreparedStatement内存泄露现象

  1. 现象

    • 200并发压测压10分钟左右内存吃满
    • jmap -histo | head 20 发现有大量的Connection对象和T4CPreparedStatement以及部分Finalizer对象,dump下来分析发现Connection和PreparedStatement这些对象都是no gc root
    • 线程dump发现finalizer线程时常blocked在connection.isReallyClosed,而拿着锁的线程在执行数据库读写

      "Finalizer" - Thread t@3   java.lang.Thread.State: BLOCKED    at oracle.jdbc.driver.PhysicalConnection.isClosed(PhysicalConnection.java:1508)    - waiting to lock <785dbebb> (a oracle.jdbc.driver.T4CConnection) owned by "http-bio-80-exec-19" t@70    at org.logicalcobwebs.proxool.ProxyConnection.isReallyClosed(ProxyConnection.java:206)    at org.logicalcobwebs.proxool.WrappedConnection.invoke(WrappedConnection.java:114)    at org.logicalcobwebs.proxool.WrappedConnection.intercept(WrappedConnection.java:87)    at oracle.jdbc.internal.OracleConnection$$EnhancerByProxool$$9245683f.finalize(<generated>)    at java.lang.System$2.invokeFinalize(System.java:1213)    at java.lang.ref.Finalizer.runFinalizer(Finalizer.java:98)    at java.lang.ref.Finalizer.access$100(Finalizer.java:34)    at java.lang.ref.Finalizer$FinalizerThread.run(Finalizer.java:210)
    • 压测停后内存能释放掉

  2. 原因

    在jvm回收WrappedConnection对象时,由于代理类重写了finalize方法,WrappedConnection方法被丢进引用队列等待finalizer线程执行finalize方法,finalize本身没有额外的实现,但是代理类在执行该方法之前会做一个isClose的判断,而jdbc oracle的实现类则使用了synchronize修饰了isClose,导致业务逻辑从池里拿出来该连接使用的时候会与finalize线程竞争该锁,一旦业务逻辑处于繁忙状态则finalizer线程执行的频率大大减小,此时在队列中的引用依然存在,对象仍然会在堆中存活。

  3. 分析

    • 为啥会出现大量T4CPreparedStatement和WrappedConnection的对象在内存不被释放

      从堆dump来看,T4CPreparedStatement占用最大的字节数,基本维持在1G以上,依据这个信息查看ibatis源码(其中我们这边在ibatis上层还封装了一个分库分表组件,而且是第一次使用,分库分表组件是当时重点查看的方向),发现T4CPreparedStatement这个对象生命周期只会在Ibatis的Executor这一层的doQuery和doUpdate这里,所以重点怀疑了数据库执行缓慢导致GC回收多次回收不掉从而被丢到老年代;通过打印耗时排除了这个想法。

      之后突然发现这些对象都是no gc root,这样的话按照GC规则没道理不被释放吧?

    • 为啥这些对象是no gc root还不被释放

      之前完全没接触到no gc root不被释放是什么情况,然后还是碰巧地从线程堆栈中发现了Finalizer线程处于Blocked状态(调优目前都靠灵光一闪。。有好的调优方法的同学麻烦分享下),然后确定方向大致是要么是proxool或者是oracle实现Connection时做了锁操作或者更多的耗时操作,然后根据线程dump查看proxool的源码发现proxool的代理类每个方法(包括finalize)都会调用isReallyClosed会调用到oracle的加锁方法isClosed,然后也google相关信息发现已经有同学提供了补丁。

    • 为啥会伴随这么多的Finalizer对象

      每次GC完,JVM发现该对象实现了finalize方法,就会封装一个Finalizer对象,将待回收对象的引用丢给Finalizer对象,然后将Finalizer对象放到引用队列,Finalizer线程每次从该队列取出Finalizer对象执行引用的finalize方法。所以在上述情况基本每个finalize方法都很慢的情况下,会导致大量的Finalizer在引用队列,并且其中应用的待回收对象也在堆中无法释放。

  4. 参考

    • http://www.majin163.com/2014/04/26/jvm-finalize/ (这个里面有句话是“它本身是个级别较低的线程”,它指的是Finalizer线程,这里要纠正下,Finalizer线程级别是Max-2也就是8,而线程默认值是5,所以这里跟线程优先级别是没太大关系的,主要还是执行太慢)
    • http://blog.csdn.net/pdw2009/article/details/7864932#
    • https://sourceforge.net/p/proxool/bugs/45/

创建代理对象blocked问题

  1. 现象

    • 在解决完proxool导致的内存无法回收问题之后,再来压测,发现TPS和响应时间还是不理想,主要耗时还是集中在数据库操作从ibatis模块往下的部分,而且dump出来由很多如下blocked
      proxool blocked
  2. 解决

    • 线程dump大部分线程处于数据库操作,然后和DBA对比SQL执行耗时无阻塞SQL或耗时SQL
    • 从ibatis模块往下使用aspectj weaver一段段代码切入查看耗时,发现从proxool层往下openSession、session.execute这两个操作都偶尔存在大量耗时
    • 有了前面的经验,严重怀疑proxool的问题,从线程dump看出多半是proxool使用cglib创建代理对象时等锁,而且看实现基本每次preparedStatement都要创建一个,另外再回想起代理类里面的基本每个操作都执行下isRealyClose且带锁的操作,决定使用druid换掉proxool,TPS翻倍,耗时减半
  3. 参考

    • https://github.com/alibaba/druid/wiki/%E5%90%84%E7%A7%8D%E6%95%B0%E6%8D%AE%E5%BA%93%E8%BF%9E%E6%8E%A5%E6%B1%A0%E5%AF%B9%E6%AF%94

总结

不要使用proxool!

不要使用proxool!

不要使用proxool!

最起码oracle数据库就别用proxool了,已经无人维护状态,一开始看druid的官方文档说oracle的别用proxool一直以为他们是在自卖自夸,通过实践证明,确实不要用。

以下是druid的给出的说明。

PSCache是数据库连接池的关键指标。在Oracle中,类似SELECT NAME FROM USER WHERE ID = ?这样的SQL,启用PSCache和不启用PSCache的性能可能是相差一个数量级的。Proxool是不支持PSCache的数据库连接池,如果你使用Oracle、SQL Server、DB2、Sybase这样支持游标的数据库,那你就完全不用考虑Proxool。

0 0