MaxDirectMemory是JVM的一个运行时参数,用于设置Java程序可以直接分配的本机(非堆)内存的最大容量。直接内存通常与Java NIO包中的直接字节缓冲区(Direct ByteBuffer)关联,这类缓冲区不由JVM的垃圾收集器管理,而是直接在操作系统层面分配和释放。这意味着直接内存的使用不会直接影响到Java堆内存的大小,但它同样受限于物理机器的内存资源。当应用程序使用ByteBuffer.allocateDirect()方法分配直接内存时,这部分内存的分配和回收不遵循Java堆内存的常规机制,可能导致内存管理更加复杂。如果没有适当的限制,直接内存的过度使用可能导致系统级别的内存不足,影响整个系统的稳定性和其他运行中的进程。
通过设置-XX:MaxDirectMemorySize参数,开发者可以为直接内存的使用设定一个上限,以防止因直接内存泄漏或过度使用而导致的系统不稳定或崩溃。
例如,可以使用如下JVM启动参数来设定直接内存的最大值为1GB:
-XX:MaxDirectMemorySize=1g 如果应用程序尝试分配的直接内存超过了这个限制,JVM将会抛出OutOfMemoryError,类似于堆内存溢出时的行为,从而强制开发者关注并处理直接内存的使用效率和限制问题。
示例程序: MaxDirectMemory
/***
* donnie4w <donnie4w@gmail.com>
* https://github.com/donnie4w/jvmtut
*
* java -XX:MaxDirectMemorySize=10M io.donnie4w.jvm.MaxDirectMemory
*/
public class MaxDirectMemory {
private static final int ALLOCATION_SIZE = 1024 * 1024; // 每次分配1MB
private static final AtomicLong totalAllocated = new AtomicLong(0); // 记录总分配量
private static final Unsafe unsafe; // 声明Unsafe实例
static {
try {
// 获取Unsafe类中的一个名为"theUnsafe"的私有静态字段
// Unsafe类提供了对JVM底层操作的能力,如直接内存访问等,通常不建议直接使用
Field theUnsafe = Unsafe.class.getDeclaredField("theUnsafe");
// 设置该字段为可访问,即使它是私有的
theUnsafe.setAccessible(true);
// 通过反射获取该字段的值,从而得到Unsafe类的一个实例
// 这是获取Unsafe实例的标准(尽管是非官方推荐的)方式
unsafe = (Unsafe) theUnsafe.get(null);
} catch (Exception e) {
// 如果在获取Unsafe实例过程中出现任何异常(如反射访问权限问题)
// 抛出运行时异常,附带原始异常信息,以确保问题不会被静默忽略
throw new RuntimeException("获取Unsafe实例失败", e);
}
}
public static void main(String[] args) throws Exception {
// 打印初始直接内存使用情况(这里仅为示意,实际直接内存使用量不易直接获取)
printDirectMemoryUsage();
try {
// 尝试分配直到发生内存溢出
List<ByteBuffer> list = new ArrayList<>();
while (true) {
ByteBuffer buffer = ByteBuffer.allocateDirect(ALLOCATION_SIZE);
totalAllocated.addAndGet(ALLOCATION_SIZE);
list.add(buffer);
}
} catch (OutOfMemoryError e) {
System.err.printf("直接内存分配达到限制,发生OutOfMemoryError:%s%n",e);
}
// 再次尝试打印直接内存使用情况
printDirectMemoryUsage();
}
/**
* 打印直接内存使用的示意方法。注意:此方法并不能真正反映出直接内存的使用情况,
* 因为直接获取直接内存使用量在JVM中通常是不可行的。这里仅用于演示。
*/
private static void printDirectMemoryUsage() {
// 以下代码仅为示意,并不能准确反映直接内存使用量
long directMemoryUsed = totalAllocated.get();
System.out.printf("当前已记录直接内存分配量: %d MB%n", directMemoryUsed / (1024 * 1024));
}
}
说明:
执行:
java -XX:MaxDirectMemorySize=10M io.donnie4w.jvm.MaxDirectMemory
执行结果:
当前已记录直接内存分配量: 0 MB
直接内存分配达到限制,发生OutOfMemoryError:java.lang.OutOfMemoryError: Cannot reserve 1048576 bytes of direct buffer memory (allocated: 10485760, limit: 10485760)
当前已记录直接内存分配量: 10 MB
直接内存(Direct Memory)在java中有非常重要的作用,Java NIO基于直接内存,实现直接缓冲区(Direct Buffers)。
直接内存的高效性原因:
Netty的高性能很大程度上归功于它对直接缓冲区(Direct Buffers)的优秀封装和利用。大致说明如下:
Netty的零拷贝技术实现,不直接等同于传统意义上的零拷贝(mmap与sendfile),而是利用Java NIO和直接内存来减少数据拷贝,间接实现类似零拷贝的效果(注意,这里只讨论netty的实现,FileChannel.transferTo接口非netty实现):
public class CompositeBufferExample {
public static void main(String[] args) {
// 创建两个ByteBuf
ByteBuf buf1 = Unpooled.buffer(4);
buf1.writeBytes(new byte[]{1, 2, 3, 4});
ByteBuf buf2 = Unpooled.buffer(4);
buf2.writeBytes(new byte[]{5, 6, 7, 8});
// 创建CompositeByteBuf
CompositeByteBuf composite = Unpooled.compositeBuffer();
// 将两个ByteBuf添加到组合缓冲区
composite.addComponents(true, buf1, buf2);
// 打印组合缓冲区的数据
printBuffer(composite);
// 清理资源
composite.release();
}
private static void printBuffer(ByteBuf buffer) {
StringBuilder sb = new StringBuilder();
for (int i = buffer.readerIndex(); i < buffer.writerIndex(); i++) {
sb.append(buffer.getByte(i)).append(",");
}
System.out.println("Combined buffer as bytes: [" + sb.deleteCharAt(sb.length() - 1) + "]");
}
}
public class ScatteringReadExample {
public static void main(String[] args) throws Exception {
RandomAccessFile file = new RandomAccessFile("./jvmtut/test1.txt", "r");
FileChannel channel = file.getChannel();
ByteBuffer body1 = ByteBuffer.allocate(10);
ByteBuffer body2 = ByteBuffer.allocate(20);
ByteBuffer[] buffers = {body1, body2};
long bytesRead = channel.read(buffers);
System.out.println("Bytes read: " + bytesRead);
for (ByteBuffer buffer : buffers) {
buffer.flip();
while (buffer.hasRemaining()) {
System.out.print((char) buffer.get());
}
}
// 清理资源
channel.close();
file.close();
}
}
public class GatheringWriteExample {
public static void main(String[] args) throws Exception {
RandomAccessFile fromFile = new RandomAccessFile("./jvmtut/test1.txt", "r");
FileChannel fromChannel = fromFile.getChannel();
RandomAccessFile toFile = new RandomAccessFile("./jvmtut/test2.txt", "rw");
FileChannel toChannel = toFile.getChannel();
ByteBuffer body1 = ByteBuffer.allocate(10);
ByteBuffer body2 = ByteBuffer.allocate(20);
ByteBuffer[] buffers = {body1, body2};
long bytesRead = fromChannel.read(buffers);
System.out.println("Bytes read: " + bytesRead);
for (ByteBuffer buffer : buffers) {
buffer.flip();
while (buffer.hasRemaining()) {
System.out.print((char) buffer.get());
}
buffer.flip(); //将position(当前写入位置或读取位置)设置为0,从缓冲区的开始位置读取所有之前写入的数据
}
toChannel.write(buffers);
// 清理资源
fromChannel.close();
toChannel.close();
fromFile.close();
toFile.close();
}
}