天天看点

JVM源码分析之不可控的堆外内存

打开directbytebuffer这个类,我们会发现有5个构造函数

我们从java层面创建directbytebuffer对象,一般都是通过bytebuffer的allocatedirect方法

也就是会使用上面提到的第一个构造函数,即

而这个构造函数里的<code>bits.reservememory(size, cap)</code>方法会做堆外内存的阈值check

因此当我们已经分配的内存超过阈值的时候会触发一次gc动作,并重新做一次分配,如果还是超过阈值,那将会抛出oom,因此分配动作会失败。

所以从这一切看来,只要设置了<code>-xx:maxdirectmemorysize=1g</code>是不会出现超过这个阈值的情况的,会看到不断的做gc。

那其他的构造函数主要是用在什么情况下的呢?

我们知道directbytebuffer回收靠的是里面有个cleaner的属性,但是我们发现有几个构造函数里cleaner这个属性却是null,那这种情况下他们怎么被回收呢?

那下面请大家先看下directbytebuffer里的这两个函数:

从名字和实现上基本都能猜出是干什么的了,slice其实是从一块已知的内存里取出剩下的一部分,用一个新的directbytebuffer对象指向它,而duplicate就是创建一个现有directbytebuffer的全新副本,各种指针都一样。

因此从这个实现来看,后面关联的堆外内存其实是同一块,所以如果我们做统计的时候如果仅仅将所有directbytebuffer对象的capacity加起来,那可能会导致算出来的结果偏大不少,这其实也是我查的那个问题,本来设置了阈值1g,但是发现达到了7g的效果。所以这种情况下使用的构造函数,可以让cleaner为null,回收靠原来的那个directbytebuffer对象被回收。

但是还有种情况,也是本文要讲的重点,在jvm里可以通过jni方法回调上面的directbytebuffer构造函数,这个构造函数是

而调用这个构造函数的jni方法是<code>jni_newdirectbytebuffer</code>

想象这么种情况,我们写了一个native方法,里面分配了一块内存,同时通过上面这个方法和一个directbytebuffer对象关联起来,那从java层面来看这个directbytebuffer确实是一个有效的占有不少native内存的对象,但是这个对象后面关联的内存完全绕过了maxdirectmemorysize的check,所以也可能给你造成这种现象,明明设置了maxdirectmemorysize,但是发现directbytebuffer关联的堆外内存其实是大于它的。

个人公众号:

JVM源码分析之不可控的堆外内存