[讨论] 关于NIO的ByteBuffer分配的本地内存只有fullGC才能回收到的问题
RednaxelaFX
2012-01-17
你的理解完全歪掉了…多半是因为我说得太简短。
我说的就是因为你的实验代码从来没调用过Bits.reserveMemory(),所以你用反射拿到的值跟最后真的在分配DirectByteBuffer时用的值根本没关系。 当你第一次调用ByteBuffer.allocateDirect()时,Bits.reserveMemory()会被调用,这个时候Bits.maxMemory字段会再被赋值一次;原本静态初始化时的值是多少都没关系。这样解释可以理解了么? |
|
ningandjin
2012-01-17
不好意思,是我问的偏了。R大说的关于DirectBuffer的这部分初始化,我已经理解了。现在撇开DirectMemory,我们只看VM这个类,我不理解的是我的测试代码中的直接使用“sun.misc.VM.maxMemory()”获得的值和通过反射Bits类的maxMemory获取的值不一样,它们应该使用的是同一个VM类实例吧?
|
|
RednaxelaFX
2012-01-17
ningandjin 写道 R大说的关于DirectBuffer的这部分初始化,我已经理解了。
你肯定还没理解全,不然就没疑问了。当然,这里涉及的实现细节超多,不是光“类加载”啊“反射”啊啥的基本原理就完事了。 代码不看全是无法理解全的 实际上是因为调用的时机不同,使得你看到了这样的结果。 Java的整个系统有个启动过程,JVM自己有些初始化要做,做好之后核心库那边也有些初始化要做(注意两者是分开的)。初始化过程中有很多行为都跟后面已经初始化结束后的行为不一样。 Bits.maxMemory在静态初始化的时候sun.misc.VM.maxDirectMemory()返回的是一个无用的默认值。这是在很早的时候发生的,此时VM还处于booting阶段(或者说booted是false)。 等你的代码去调用sun.misc.VM.maxDirectMemory()的时候,VM已经不在booting阶段(或者说booted已经是true),因而它会走另一个分支返回真正有效的值回来。 看这个文件的181行 http://hg.openjdk.java.net/jdk6/jdk6/jdk/file/tip/src/share/classes/sun/misc/VM.java 做了个例子给你看,如果这个看不懂的话那请补习各种知识… https://gist.github.com/1624860 这个例子里我修改了sun.misc.VM的代码,让它在maxDirectMemory()的开头记录下调用它的stack trace。因为VM在booting阶段时System类还没初始化完,所以这里不能使用System.out或System.err,所以只好用绕弯的办法,把日志先记录在一个ArrayList里,回头再拿出来。 修改后的sun.misc.VM类通过-Xbootclasspath/p参数让VM读到,达到“覆盖”原本在rt.jar里这个类的目的。然后进行实验。 关键点是:实际上有效的那个值是在这次调用才被记录到sun.misc.VM.directMemory字段上的;该字段的值在这之前都是无效值: 引用 VM.maxDirectMemory(): booted = false, java.lang.Exception
at sun.misc.VM.maxDirectMemory(VM.java:168) at java.lang.System.initializeSystemClass(System.java:1107) 在此之前因为各种系统属性还没初始化好,所以sun.misc.VM.maxDirectMemory()返回的就是个无效的初始值。System.initializeSystemClass()里开头会初始化系统属性,参考http://hg.openjdk.java.net/jdk6/jdk6/jdk/file/tip/src/share/classes/java/lang/System.java 1089行。在这个初始化之后,System.getProperty()才开始有效,因而在这之后调用sun.misc.VM.maxDirectMemory()才进入了真正初始化有效的值的那个分支。 而java.nio.Bits类的静态初始化却在此之前就执行过了,所以得到的是个无效的初始值。看上面sun.misc.VM.java的源码,在189行的条件分支,s在Bits初始化时还是null,所以跳过了下面的计算。 嗯我觉得我已经写了太多废话了… |