alibaba / jetcache

JetCache is a Java cache framework.
Apache License 2.0
5.03k stars 1.05k forks source link

优化threadlocal #822

Closed zt9788 closed 11 months ago

zt9788 commented 11 months ago

大量虚拟线程下ThreadLocal可能内存泄漏 缓存类型先用weak处理

areyouok commented 11 months ago

简单看了一下,有几个问题需要讨论一下:

  1. CacheContext是否需要修改?CacheThreadLocal是个很轻量的对象,每个虚拟线程一个,每次new一个出来,问题不大。
  2. 不能只改你要用到的部分,如果kryo5 encoder/decoder 改了,那么java/kryo encoder/decoder也应该修改
  3. kryo5 encoder/decoder现在的改法需要讨论。如果虚拟线程每次都new一个出来,那么放在ThreadLocal上面的缓存对象不会有像样的的命中率。kryo对象创建开销还挺大的,outputstream里面的byte[]也不小。是不是应该做个简单的对象池(自己做不要有依赖)
zt9788 commented 11 months ago

简单看了一下,有几个问题需要讨论一下:

  1. CacheContext是否需要修改?CacheThreadLocal是个很轻量的对象,每个虚拟线程一个,每次new一个出来,问题不大。
  2. 不能只改你要用到的部分,如果kryo5 encoder/decoder 改了,那么java/kryo encoder/decoder也应该修改
  3. kryo5 encoder/decoder现在的改法需要讨论。如果虚拟线程每次都new一个出来,那么放在ThreadLocal上面的缓存对象不会有像样的的命中率。kryo对象创建开销还挺大的,outputstream里面的byte[]也不小。是不是应该做个简单的对象池(自己做不要有依赖)
  1. 不清除理论上也可以,只是会极大的延长ThreadLocal生命周期,造成少量的内存浪费
  2. 我再看一下,可能改漏了
  3. 开始也考虑对象池,考虑到复杂度增加,就先用弱引用来实现了 目前看jackson/log4j2的方案,基本是引入无锁对象池, 目前看用的比较多的有jctool的MpmcAtomicArrayQueue,自己写一个也可以(或者分2个阶段来修改?)

    我回头先把2改了 对象池的我参考一下,晚一点先简单实现一个

zt9788 commented 11 months ago

用ArrayBlockingQueue做了一个简单对象池,是否可行? 如果可行,我就用这个把2,3重新改一下

public class ObjectPool<T> {
        private final ArrayBlockingQueue<T> queue;
        private final int size;
        private final ObjectFactory<T> factory;
        public ObjectPool(int size, ObjectFactory<T> factory) {
            this.size = size;
            this.factory = factory;
            queue = new ArrayBlockingQueue<>(size);
            for (int i = 0; i < size; i++) {
                queue.add(factory.create());
            }
        }
        public T borrowObject() {
            T t = queue.poll();
            if(t == null)
                return factory.create();
            return t;
        }
        public void returnObject(T obj) {
            if (obj == null) {
                return;
            }
            queue.offer(obj);
            factory.reset(obj);
        }

        public interface ObjectFactory<T> {
            T create();
            void reset(T obj);
        }
}
areyouok commented 11 months ago

简单看了一下,有几个问题需要讨论一下:

  1. CacheContext是否需要修改?CacheThreadLocal是个很轻量的对象,每个虚拟线程一个,每次new一个出来,问题不大。
  2. 不能只改你要用到的部分,如果kryo5 encoder/decoder 改了,那么java/kryo encoder/decoder也应该修改
  3. kryo5 encoder/decoder现在的改法需要讨论。如果虚拟线程每次都new一个出来,那么放在ThreadLocal上面的缓存对象不会有像样的的命中率。kryo对象创建开销还挺大的,outputstream里面的byte[]也不小。是不是应该做个简单的对象池(自己做不要有依赖)
  1. 不清除理论上也可以,只是会极大的延长ThreadLocal生命周期,造成少量的内存浪费
  2. 我再看一下,可能改漏了
  3. 开始也考虑对象池,考虑到复杂度增加,就先用弱引用来实现了 目前看jackson/log4j2的方案,基本是引入无锁对象池, 目前看用的比较多的有jctool的MpmcAtomicArrayQueue,自己写一个也可以(或者分2个阶段来修改?) 我回头先把2改了 对象池的我参考一下,晚一点先简单实现一个

1 按我的理解,虚拟线程结束的时候,它的ThreadLocal会被gc吧。如果是这样,多个CacheThreadLoca好像问题不大?最多几十个字节。

areyouok commented 11 months ago

用ArrayBlockingQueue做了一个简单对象池,是否可行? 如果可行,我就用这个把2,3重新改一下

public class ObjectPool<T> {
        private final ArrayBlockingQueue<T> queue;
        private final int size;
        private final ObjectFactory<T> factory;
        public ObjectPool(int size, ObjectFactory<T> factory) {
            this.size = size;
            this.factory = factory;
            queue = new ArrayBlockingQueue<>(size);
            for (int i = 0; i < size; i++) {
                queue.add(factory.create());
            }
        }
        public T borrowObject() {
            T t = queue.poll();
            if(t == null)
                return factory.create();
            return t;
        }
        public void returnObject(T obj) {
            if (obj == null) {
                return;
            }
            queue.offer(obj);
            factory.reset(obj);
        }

        public interface ObjectFactory<T> {
            T create();
            void reset(T obj);
        }
}

简单来说,这样就可以了。

pool不能自动缩小的问题,就像以前output里面的字节数组一样,没有简单的好办法,我以前用weak ref,靠ygc来定期清理,也不是很好的方案。

zt9788 commented 11 months ago

用ArrayBlockingQueue做了一个简单对象池,是否可行? 如果可行,我就用这个把2,3重新改一下

public class ObjectPool<T> {
        private final ArrayBlockingQueue<T> queue;
        private final int size;
        private final ObjectFactory<T> factory;
        public ObjectPool(int size, ObjectFactory<T> factory) {
            this.size = size;
            this.factory = factory;
            queue = new ArrayBlockingQueue<>(size);
            for (int i = 0; i < size; i++) {
                queue.add(factory.create());
            }
        }
        public T borrowObject() {
            T t = queue.poll();
            if(t == null)
                return factory.create();
            return t;
        }
        public void returnObject(T obj) {
            if (obj == null) {
                return;
            }
            queue.offer(obj);
            factory.reset(obj);
        }

        public interface ObjectFactory<T> {
            T create();
            void reset(T obj);
        }
}

简单来说,这样就可以了。

pool不能自动缩小的问题,就像以前output里面的字节数组一样,没有简单的好办法,我以前用weak ref,靠ygc来定期清理,也不是很好的方案。

OK,我明天按照这个方案再改一下2,3;

CacheThreadLocal确实可以回收,嗯,这块应该影响不大,我还原一下

areyouok commented 11 months ago

还有个java encoder/decoder也需要改

CLAassistant commented 11 months ago

CLA assistant check
All committers have signed the CLA.

areyouok commented 11 months ago

这个我合并了,还有一些地方在使用synchronized,你要改吗?

zt9788 commented 11 months ago

这个我合并了,还有一些地方在使用synchronized,你要改吗?

我之前主要看了core. 那就我改吧 ,不过要下周预计周二、周三的样子