甘肃庆阳疫情最新情况徐州自动seo
文章目录
- 1. 什么是直接内存
- 2. 直接内存分配源码分析
- 3. 使用直接内存的优缺点:
1. 什么是直接内存
直接内存(Direct Memory)并不是虚拟机运行时数据区的一部分,也不是Java虚拟机规范中定义的内存区域,某些情况下这部分内存也会被频繁地使用,而且也可能导致OutOfMemoryError异常出现。
Java里用DirectByteBuffer可以分配一块直接内存(堆外内存),元空间对应的内存也叫作直接内存,它们对应的都是机器的物理内存。
package com.test;import java.nio.ByteBuffer;public class DirectMemoryTest {public static void heapAccess() {long startTime = System.currentTimeMillis();//分配堆内存ByteBuffer buffer = ByteBuffer.allocate(1000);for (int i = 0; i < 100000; i++) {for (int j = 0; j < 200; j++) {buffer.putInt(j);}buffer.flip();for (int j = 0; j < 200; j++) {buffer.getInt();}buffer.clear();}long endTime = System.currentTimeMillis();System.out.println("堆内存访问:" + (endTime - startTime) + "ms");}public static void directAccess() {long startTime = System.currentTimeMillis();//分配直接内存ByteBuffer buffer = ByteBuffer.allocateDirect(1000);for (int i = 0; i < 100000; i++) {for (int j = 0; j < 200; j++) {buffer.putInt(j);}buffer.flip();for (int j = 0; j < 200; j++) {buffer.getInt();}buffer.clear();}long endTime = System.currentTimeMillis();System.out.println("直接内存访问:" + (endTime - startTime) + "ms");}public static void heapAllocate() {long startTime = System.currentTimeMillis();for (int i = 0; i < 100000; i++) {ByteBuffer.allocate(100);}long endTime = System.currentTimeMillis();System.out.println("堆内存申请:" + (endTime - startTime) + "ms");}public static void directAllocate() {long startTime = System.currentTimeMillis();for (int i = 0; i < 100000; i++) {ByteBuffer.allocateDirect(100);}long endTime = System.currentTimeMillis();System.out.println("直接内存申请:" + (endTime - startTime) + "ms");}public static void main(String args[]) {//访问速度进行对比for (int i = 0; i < 10; i++) {heapAccess();directAccess();}System.out.println("----------");//申请速度进行对比for (int i = 0; i < 10; i++) {heapAllocate();directAllocate();}}
}
从程序运行结果看出直接内存申请较慢,但访问效率高。在java虚拟机实现上,本地IO一般会直接操作直接内存(直接内存=>系统调用=>硬盘/网卡),而非直接内存则需要二次拷贝(堆内存=>直接内存=>系统调用=>硬盘/网卡)。
2. 直接内存分配源码分析
public static ByteBuffer allocateDirect(int capacity) {return new DirectByteBuffer(capacity);}DirectByteBuffer(int cap) { super(-1, 0, cap, cap);boolean pa = VM.isDirectMemoryPageAligned();int ps = Bits.pageSize();long size = Math.max(1L, (long)cap + (pa ? ps : 0));//判断是否有足够的直接内存空间分配,可通过‐XX:MaxDirectMemorySize=<size>参数指定直接内存最大可分配空间,如果不指定默认为最大堆内存大小,//在分配直接内存时如果发现空间不够会显示调用System.gc()触发一次full gc回收掉一部分无用的直接内存的引用对象,同时直接内存也会被释放掉//如果释放完分配空间还是不够会抛出异常java.lang.OutOfMemoryErrorBits.reserveMemory(size, cap);long base = 0;try {// 调用unsafe本地方法分配直接内存base = unsafe.allocateMemory(size);} catch (OutOfMemoryError x) {// 分配失败,释放内存Bits.unreserveMemory(size, cap);throw x;}unsafe.setMemory(base, size, (byte) 0);if (pa && (base % ps != 0)) {// Round up to page boundaryaddress = base + ps - (base & (ps - 1));} else {address = base;}// 使用Cleaner机制注册内存回收处理函数,当直接内存引用对象被GC清理掉时,// 会提前调用这里注册的释放直接内存的Deallocator线程对象的run方法cleaner = Cleaner.create(this, new Deallocator(base, size, cap));att = null;}public final class Unsafe {// 申请一块本地内存。内存空间是未初始化的,其内容是无法预期的。// 使用freeMemory释放内存,使用reallocateMemory修改内存大小public native long allocateMemory(long bytes);}//jvm源码 openjdk8/hotspot/src/share/vm/prims/unsafe.cppUNSAFE_ENTRY(jlong, Unsafe_AllocateMemory(JNIEnv *env, jobject unsafe, jlong size))UnsafeWrapper("Unsafe_AllocateMemory");size_t sz = (size_t)size;if (sz != (julong)size || size < 0) {THROW_0(vmSymbols::java_lang_IllegalArgumentException());}if (sz == 0) {return 0;}sz = round_to(sz, HeapWordSize);// 调用os::malloc申请内存,内部使用malloc这个C标准库的函数申请内存void* x = os::malloc(sz, mtInternal);if (x == NULL) {THROW_0(vmSymbols::java_lang_OutOfMemoryError());}//Copy::fill_to_words((HeapWord*)x, sz / HeapWordSize);return addr_to_java(x);UNSAFE_END
3. 使用直接内存的优缺点:
优点:
- 不占用堆内存空间,减少了发生GC的可能
- java虚拟机实现上,本地IO会直接操作直接内存(直接内存=>系统调用=>硬盘/网卡),而非直接内存则需要二次拷贝(堆内存=>直接内存=>系统调用=>硬盘/网卡)
缺点:
- 初始分配较慢
- 没有JVM直接帮助管理内存,容易发生内存溢出。为了避免一直没有FULL GC,最终导致直接内存把物理内存耗完。我们可以指定直接内存的最大值,通过
-XX:MaxDirectMemorySize
来指定,当达到阈值的时候,调用system.gc来进行一次FULL GC,间接把那些没有被使用的直接内存回收掉。