DisableExplicitGC和Direct ByteBuffer研究

本文探讨了DisableExplicitGC标志如何影响System.gc()调用,防止其触发full GC,以及在Oracle/Sun JDK中DirectByteBuffer的内存管理机制。强调了在使用DirectByteBuffer时,DisableExplicitGC可能导致的direct memory OOM问题,并提供了相应的解决方案。

目录:

1. DisableExplicitGC介绍

2. Direct  ByteBuffer介绍

 

一,DisableExplicitGC介绍

-XX:+DisableExplicitGC标志自动将System.gc()调用转换成一个空操作,就是应用中调用System.gc()会变成一个空操作。最主要的原因是为了防止某些手贱的同学在代码里到处写System.gc()的调用而干扰了程序的正常运行吧。有些应用程序本来可能正常跑一天也不会出一次full GC,但就是因为有人在代码里调用了System.gc()而不得不间歇性被暂停。

应用中调用System.gc()会有啥影响?

在Oracle/Sun JDK这个具体实现上,System.gc()的默认效果是引发一次stop-the-world的full GC,对整个GC堆做收集,对于应用的影响可想而知。

DisableExplicitGC这个设置了会有啥坑?

其中一种情况是下述三个条件同时满足时会发生的: 

1、应用本身在GC堆内的对象行为良好,正常情况下很久都不发生full GC; 

2、应用大量使用了NIO的direct memory,经常、反复的申请DirectByteBuffer 

3、使用了-XX:+DisableExplicitGC 

二,Direct  ByteBuffer介绍

注意事项:

1、DirectByteBuffer没有finalizer,它的native memory的清理工作是通过sun.misc.Cleaner自动完成的。 

2、sun.misc.Cleaner是一种基于PhantomReference的清理工具,比普通的finalizer轻量些。

做full GC的话会对old gen做reference processing,进而能触发Cleaner对已死的DirectByteBuffer对象做清理工作。

而如果很长一段时间里没做过GC或者只做了young GC的话则不会在old gen触发Cleaner的工作,

那么就可能让本来已经死了的、但已经晋升到old gen的DirectByteBuffer关联的native memory得不到及时释放。

3、为DirectByteBuffer分配空间过程中会显式调用System.gc(),以期通过full GC来强迫已经无用的DirectByteBuffer对象释放掉它们关联的native memory: 

这几个实现特征使得Oracle/Sun JDK 6依赖于System.gc()触发GC来保证DirectByteMemory的清理工作能及时完成。

如果打开了-XX:+DisableExplicitGC,清理工作就可能得不到及时完成,于是就有机会见到direct memory的OOM。

 

使用了Direct  ByteBuffer应该咋处理?

如果你在使用Oracle/Sun JDK 6,应用里有任何地方用了direct memory,那么使用-XX:+DisableExplicitGC要小心。

如果用了该参数而且遇到direct memory的OOM,可以尝试去掉该参数看是否能避开这种OOM。

如果担心System.gc()调用造成full GC频繁,可以尝试下面提到 -XX:+ExplicitGCInvokesConcurrent 参数 

( -XX:+ExplicitGCInvokesConcurrent  System.gc()和应用程序并发执行,就是不stop-the-world)

-XX:+ExplicitGCInvokesConcurrent 或 -XX:+ExplicitGCInvokesConcurrentAndUnloadsClasses 

这两个参数也是用来改变System.gc()的默认行为用的;不同的是这两个参数只能配合CMS使用(-XX:+UseConcMarkSweepGC),

而且System.gc()还是会触发GC的,只不过不是触发一个完全stop-the-world的full GC,而是一次并发GC周期。 

CMS GC周期中也会做reference processing。

所以如果用这两个参数的其中一个,而不是用-XX:+DisableExplicitGC的话,就避开了由full GC带来的长GC pause,

同时NIO direct memory的OOM也不会那么容易发生。

 

MaxDirectMemorySize 与 NIO direct memory 的默认上限 

-XX:MaxDirectMemorySize 是用来配置NIO direct memory上限用的VM参数。

 MaxDirectMemorySize没显式配置的时候,NIO direct memory可申请的空间的上限就是-Xmx减去一个survivor space的预留大小。 

HeapDump位置问题

-XX:+HeapDumpBeforeFullGC、-XX:+HeapDumpAfterFullGC、-XX:+HeapDumpOnOutOfMemoryError 这几个参数可以在不同条件下做出HPROF格式的heap dump。

如果不想费神去摸索到底各种环境被配置成什么样、“working directory”到底在哪里的话,就在VM启动参数

 

<think>我们正在讨论Java NIO中的ByteBuffer。用户的问题是如何重置ByteBuffer的position设置capacity。首先,我们需要明确:ByteBuffer的capacity在创建后通常是固定的(除了通过slice()、duplicate()等方法创建视图,或者使用asReadOnlyBuffer()等,但这些方法不会改变底层数组的capacity)。如果要改变容量,通常需要创建一个新的ByteBuffer。但是,我们可以重置position(即设置position为0)以及调整limit等。另外,compact()方法可以压缩缓冲区,将position到limit之间的数据移动到缓冲区的开头,然后将position设置为剩余数据的末尾,limit设置为capacity。这样,我们就可以继续向缓冲区写入数据,而不会覆盖未读的数据。根据引用[3]:在HeapByteBuffer中的compact实现,它通过System.arraycopy将position到limit的数据复制到缓冲区的开头,然后设置position为剩余数据的数量(remaining()),设置limit为capacity。用户的问题包含两个部分:1.重置position:我们可以使用position(intnewPosition)方法设置position,或者使用rewind()方法将position设置为0(同时清除mark)。2.设置capacity:实际上,ByteBuffer的capacity在创建后不能改变。但是,我们可以通过创建一个新的ByteBuffer并复制数据来改变容量。因此,我们可以这样操作:重置position:-使用`buffer.position(0);`将position设置为0。-或者使用`buffer.rewind();`将position设置为0,同时limit不变(通常用于重新读取数据)。改变capacity:-如果我们需要更大的容量,可以创建一个新的ByteBuffer(可以是HeapByteBufferDirectByteBuffer),然后将原缓冲区中的数据复制到新缓冲区中。-复制数据时,我们可以使用`put`方法,但要注意先重置原缓冲区的位置(使用rewind()),然后读取原缓冲区的数据(从0到limit)写入新缓冲区。另外,引用[1]提到:mark()方法可以标记当前位置,然后通过reset()方法可以回到标记的位置。但是,重置position设置capacity并不直接依赖于mark。步骤:1.重置position(不改变capacity):-使用`buffer.rewind();`:将position设置为0,limit保持不变,mark被丢弃。这适用于重新读取缓冲区。-使用`buffer.clear();`:将position设置为0,limit设置为capacity,mark被丢弃。这适用于重新写入缓冲区。2.改变capacity(需要创建新缓冲区):-创建一个新的ByteBuffer,指定新的容量。-将原缓冲区中的数据复制到新缓冲区。复制前,需要调整原缓冲区的positionlimit(例如,使用`flip()`或`rewind()`)以便读取全部或部分数据。示例代码:重置position: ```javaByteBufferbuffer =...;buffer.rewind();//将position设置为0,limit不变//或者buffer.clear();//将position设置为0,limit设置为capacity```改变capacity(扩大或缩小): ```java//假设我们需要将容量扩大到newCapacityintnewCapacity= ...; ByteBuffer newBuffer =ByteBuffer.allocate(newCapacity); //或者allocateDirect//将原缓冲区切换到读模式buffer.flip();//设置limit为当前position,position为0(这样读取的数据就是之前写入的数据) //或者如果原缓冲区已经处于读模式,我们可以使用rewind(),但要注意不要读取超过limit//将原缓冲区的数据复制到新缓冲区newBuffer.put(buffer);//现在newBuffer包含了原缓冲区的数据,并且position指向最后写入的位置,limit为capacity//如果我们需要读取新缓冲区的数据,需要调用flip()newBuffer.flip(); ```注意:如果原缓冲区是直接缓冲区,新缓冲区可以是直接缓冲区或堆缓冲区,反之亦然。另外,如果原缓冲区中的数据量大于新容量,那么复制时会抛出BufferOverflowException。因此,在复制前需要确保新容量足够。另外,引用[5]提到:getByte(intindex)方法可以从指定位置读取,而不改变position。但是,我们这里不涉及。总结:重置position:使用`rewind()`或`clear()`,或者使用`position(0)`。改变capacity:需要创建新的ByteBuffer并复制数据。因此,回答用户的问题:如何在JavaNIO中重置ByteBuffer的positioncapacity?重置position:直接调用`rewind()`或`clear()`或`position(0)`。改变capacity:不能直接改变,需要创建新的ByteBuffer并复制数据。下面给出完整代码示例。</think>### Java NIO ByteBuffer 的 position 重置与 capacity 设置 在 Java NIO 中,`ByteBuffer` 的 **position** **capacity** 是两个核心属性: - **position**:当前读写位置指针 - **capacity**:缓冲区的最大容量(创建后固定不变) #### 1. 重置 position 的三种方法 ```java ByteBuffer buffer = ByteBuffer.allocate(1024); // 方法1:rewind() - 重置position到0,limit保持不变 buffer.rewind(); // position=0, limit不变 // 方法2:clear() - 重置position到0,limit=capacity buffer.clear(); // position=0, limit=capacity // 方法3:position() - 精确设置position值 buffer.position(0); // 手动设置position=0 ``` #### 2. "改变 capacity" 的实际操作 由于 `capacity` 在创建后不可修改,要实现类似效果需创建新缓冲区并复制数据: ```java // 原始缓冲区 ByteBuffer original = ByteBuffer.allocate(256); original.put("Hello".getBytes()); original.flip(); // 准备读取 position=0, limit=5 // 创建更大容量的新缓冲区 int newCapacity = 512; ByteBuffer newBuffer = ByteBuffer.allocate(newCapacity); // 复制数据(使用绝对位置操作避免改变position) for (int i = 0; i < original.limit(); i++) { newBuffer.put(i, original.get(i)); // get(i)不改变position[^5] } // 设置新缓冲区的读写位置 newBuffer.position(original.position()); newBuffer.limit(original.limit()); ``` #### 3. 高效数据迁移方法 使用 `compact()` 压缩数据并重置位置(适合重用缓冲区): ```java ByteBuffer buffer = ByteBuffer.allocate(100); buffer.put("Data".getBytes()); buffer.flip(); // 压缩缓冲区:将未读数据移到头部 buffer.compact(); // position=剩余空间起始位, limit=capacity[^3] // 继续写入新数据 buffer.put("Append".getBytes()); buffer.flip(); // 准备读取 ``` #### 4. 关键概念说明 | 方法 | 作用 | position变化 | limit变化 | |------------|-------------------------------|-------------|----------| | `rewind()` | 重置position到0 | 0 | 不变 | | `clear()` | 重置position到0,limit=capacity | 0 | capacity | | `flip()` | limit=position, position=0 | 0 | 原position | | `compact()`| 压缩未读数据到缓冲区头部 | 剩余数据长度 | capacity | #### 5. 最佳实践建议 1. **读取后重置**: ```java buffer.mark(); // 标记当前位置[^1] buffer.get(); // 读取1字节(position自增) buffer.reset(); // 回到标记位置 ``` 2. **批量复制**: ```java ByteBuffer[] buffers = new ByteBuffer[3]; // ...初始化buffers... channel.read(buffers); // 分散读取[^4] ``` 3. **直接缓冲区**(高性能场景): ```java ByteBuffer directBuffer = ByteBuffer.allocateDirect(1024); ``` 4. **容量检查**: ```java if (buffer.remaining() < requiredSize) { // 创建新缓冲区并迁移数据 } ``` > **重要限制**:`capacity` 在 `ByteBuffer` 实例创建后不可修改,这是由底层数组的固定性决定的[^2]。需要"改变容量"时,必须创建新实例并复制数据。 ### 应用场景示例 **网络数据包重组**: ```java ByteBuffer packetBuffer = ByteBuffer.allocate(2048); // 接收数据片段 while (channel.read(packetBuffer) > 0) { packetBuffer.flip(); // 处理完整数据包 while (packetBuffer.remaining() >= PACKET_SIZE) { processPacket(packetBuffer); } // 压缩剩余数据 packetBuffer.compact(); // 未读数据移到头部 } ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值