OkHttpClient中StreamAllocation泄露

何为StreamAllocation泄露?

上一篇文章ttps://www.jianshu.com/p/3688967830fe说了,每次请求都会将StreamAllocation和其对应的Connection都会建立起关系:StreamAllocation有成员变量connection,Connection#allocations中持有StreamAllocation的弱引用。每次请求完毕,用户正常操作Response,就会解除StreamAllocation和Connection的这种关系,这样Connection就会被其它的请求复用。所谓的"用户正常操作Response"包含3个方面:

  • 如果响应流中没有数据,用户不需要做任何操作,OkHttp内部会自动解除关系;
  • 用户读取完响应流中的所有数据;
  • 用户读没有取完响应流中的所有数据,但是调用了Response#close。

如果没有满足上面三个条件,在Call#execute执行完毕用户的调用返回,返回之后正好发生了一次Full GC,分配给该Call的StreamAllocation被回收,此时由于StreamAllocation和Connection的关系没有被解除。Connection中还包含StreamAllocation的弱引用对应的对象,此时Connection不能被其它请求复用,这样就发生了StreamAllocation泄露。

一个栗子

对两个服务发起http请求,第一个请求由响应body,但是请求完之后用户不会读取相应body的数据也不会调用response#close,马上调用System.gc(),这样第一个请求就人为的制造了StreamAllocation泄露。下面代码执行的结果为:


image.png
public class TestOkGet {

    private static OkHttpClient client = new OkHttpClient.Builder()
            .readTimeout(10, TimeUnit.SECONDS)
            .writeTimeout(10, TimeUnit.SECONDS)
            .build();
    static{
        Logger.getLogger(OkHttpClient.class.getName()).setLevel(Level.FINE);
    }

    public static void main(String[] args) throws InterruptedException, IOException {
        String getPath = "http://localhost:7777/TestServlet";
        // 在Springboot-test 这个工程中。。。
        String getPath1 = "http://localhost:8888/boot/properties/testGet";

        okGet(getPath);
        // 这个调用有可能触发FullGC也有可能不会触发FullGC,和具体虚拟机的实现有关
        // 参考https://www.zhihu.com/question/38551124
        // Hotspot虚拟机,在没有配置"-XX:-+DisableExplicitGC"调用System.gc()会马上触发FullGC
        System.gc();
        Thread.sleep(1000);
        okGet(getPath1);
    }


    public static void okGet(String path) {
        Request request = new Request.Builder().url(path).build();
        Call call = client.newCall(request);
        try {
            Response response = call.execute();
            System.out.println(response.code());
//            System.out.println(response.body().string());
//            response.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

StreamAllocation泄露检测原理

正常情况下请求正常结束读取完响应body的数据或者调用response#close,会调用StreamAllocation#deallocate来解除StreamAllocation和Connection之间的关系,即将当前的Reference<StreamAllocation>从Connection#allocations中移除。

如果请求完之后没有读取完响应body的数据并且没有调用response#close就不会解除相关关系,这样在ConnectionPool做下次空闲连接检测时,发现该Reference<StreamAllocation>已经没有强引用关联了,就会报出StreamAllocation泄漏的警告。具体逻辑是在:ConnectioPool#pruneAndGetAllocationCount这个方法中。这也是为什么上面的代码要发送两次Http请求,第二次发起Http请求的目的只有一个,那就是促使ConnectionPool做一次空闲检测。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

友情链接更多精彩内容