[讨论] 关于NIO的ByteBuffer分配的本地内存只有fullGC才能回收到的问题

ningandjin 2012-01-14
各位新年快乐啊;

    这是别人定位过的一个问题,问题虽然解决了,但是解释我还不能完全信服,所以到这儿来请教请教各位大牛
.   问题是这样的啊,JVM设置了15G的内存,JVM配置的目的是不发生FullGC,但是系统运行2天的时候,发现服务器的内存被占用了30G,但是JConsole,Jmap去看JVM的heap,没有任何异常,通过查看服务内存信息,发现有一个13G的堆外空间被占用,里面存储的信息都是NIO客户端曾经发送过的信息。调查发现,NIO会使用ByteBuffer
.allocate()分配堆外内存,用作发送消息的缓冲区使用,然后因为一直没有发生FullGC,这部分内存就一直没有得到释放,就导致了这个内存占用到30G的情况,当最后发生FullGC的时候,所有的内存都回收到了。所以他们解决问题的方法就是配置了一个定时fullGC参数。每隔一个小时或者几个小时fullGC
一次,但是我不明白的是,为什么MinorGC没有把这个堆外内存回收到啊?JVM对堆外内存的管理方式是怎样的啊?希望大大能够推荐点资料,加深一下理解,谢谢。
RednaxelaFX 2012-01-15
请阅读置顶帖,http://hllvm.group.iteye.com/group/topic/27945
第一个案例。
ningandjin 2012-01-15
RednaxelaFX 写道
请阅读置顶帖,http://hllvm.group.iteye.com/group/topic/27945
第一个案例。

直接命中要害,谢谢!!我好好消化下,
ningandjin 2012-01-16
RednaxelaFX 写道
请阅读置顶帖,http://hllvm.group.iteye.com/group/topic/27945
第一个案例。


JDK version:
java version "1.6.0_21"
Java(TM) SE Runtime Environment (build 1.6.0_21-b07)
Java HotSpot(TM) Client VM (build 17.0-b17, mixed mode, sharing)

OS:
windows 7,内存2G

R大,再问个问题啊,
System.out.println(sun.misc.VM.maxDirectMemory());
       
        Class c = Class.forName("java.nio.Bits");
        Field maxMemory = c.getDeclaredField("maxMemory");
        maxMemory.setAccessible(true);
        synchronized (c) {
            Long maxMemoryValue = (Long)maxMemory.get(null);
            System.out.println("maxMemoryValue:"+maxMemoryValue);
        }

执行结果如下:
maxMemoryValue:259522560
maxMemoryValue:67108864

看java.nio.Bits代码
// A user-settable upper limit on the maximum amount of allocatable
    // direct buffer memory.  This value may be changed during VM
    // initialization if it is launched with "-XX:MaxDirectMemorySize=<size>".
    private static volatile long maxMemory = VM.maxDirectMemory();

一样的逻辑处理,为什么2者会差别这么大啊?
RednaxelaFX 2012-01-16
嗯?你的代码跟你的输出结果看起来不对应。
顺带请看看置顶帖里的第5点。
ningandjin 2012-01-16
RednaxelaFX 写道
嗯?你的代码跟你的输出结果看起来不对应。
顺带请看看置顶帖里的第5点。


sorry,为了结果看起来对称,改动了第一行代码:
System.out.println("maxMemoryValue:"+sun.misc.VM.maxDirectMemory());

第5点我也看过了,而且测试过了,的确是按R大说的分配的,于是我想测一下不配置任何参数下的效果,结果看到有人说默认是64M,于是我把2个方法放在一起做了比较,发现2个结果不一样,但是从源代码来看,2者都是调用的VM.maxDirectMemory(),很费解,所以上来求教于R大。
RednaxelaFX 2012-01-16
那是因为你用反射去获取java.nio.Bits.maxMemory的值的时候,这个字段还没最终初始化。最终用的值是这样初始化的:
if (!memoryLimitSet && VM.isBooted()) {
    maxMemory = VM.maxDirectMemory();
    memoryLimitSet = true;
}

在Bits.reserveMemory()里。读代码别只看一半。

你试试在反射获取这个值之前先随便调个ByteBuffer.allocateDirect(...)

D:\experiment>\sdk\groovy-1.7.2\bin\groovysh
Groovy Shell (1.7.2, JVM: 1.6.0_26)
Type 'help' or '\h' for help.
-----------------------------------------------------------------
groovy:000> sun.misc.VM.maxDirectMemory()
===> 1037959168
groovy:000> java.nio.Bits.maxMemory
===> 67108864
groovy:000> java.nio.ByteBuffer.allocateDirect(2)
===> java.nio.DirectByteBuffer[pos=0 lim=2 cap=2]
groovy:000> java.nio.Bits.maxMemory
===> 1037959168
groovy:000> quit
ningandjin 2012-01-16
R大,从代码上来看
 private static volatile long maxMemory = VM.maxDirectMemory();

我们在反射Bits类的时候,应该会加载maxMemory这个static的属性吧?如果加载,则此时会调用VM.maxDirectMemory()方法,此时调用的VM.maxDirectMemory()和我直接调用sun.misc.VM.maxDirectMemory()的差别在哪儿? 是因为VM这个类在我反射的时候还没有初始化完吗?
另外,求R大发一个能sun.misc.*包中这些的源码连接,我到网上找了好久,都没找到源码包下。十分感谢。
RednaxelaFX 2012-01-16
代码在OpenJDK里就有。
那个静态初始化会被后面第一次调用Bits.reserveMemory()的初始化刷新掉。这边需要详细解释么?
ningandjin 2012-01-17
RednaxelaFX 写道
代码在OpenJDK里就有。
那个静态初始化会被后面第一次调用Bits.reserveMemory()的初始化刷新掉。这边需要详细解释么?


R大,早上好,敬老大一杯茶 ,谢谢老大的指导。这儿我想了解一下,一个Java main函数启动的时候,在执行到我们自己写的代码之前的做了些什么。比方说这个VM类的初始化。。。。

在我的代码中:
 System.out.println("maxMemoryValue:"+sun.misc.VM.maxDirectMemory());
        
        Class c = Class.forName("java.nio.Bits");
        Field maxMemory = c.getDeclaredField("maxMemory");
        maxMemory.setAccessible(true);
        synchronized (c) {
            Long maxMemoryValue = (Long)maxMemory.get(null);
            System.out.println("maxMemoryValue:"+maxMemoryValue);
        }


2者皆没有调用到Bits.reserveMemory()这个方法啊,是不是因为Java的反射机制啊?
Global site tag (gtag.js) - Google Analytics