JVM学习笔记——内存结构篇( 九 )

我们可以明显看到directBuffer速度比IO读取快很多,那么究竟是怎么实现的
我们可以分别给出两张图进行解释:

  1. JVM正常读取

JVM学习笔记——内存结构篇

文章插图
  1. 直接内存读取

JVM学习笔记——内存结构篇

文章插图
我们由上图可以得知:
  • JVM正常读取需要先复制一份经过系统内存缓冲区,然后再复制一份才能进入到java文件中
  • DirectMemory可以同时在系统内存和java堆内存中使用,我们只需要传入数据到直接内存中就可以直接读取调用
直接内存内存溢出问题我们同样来进行直接内存的内存溢出问题测试:
package cn.itcast.jvm.t1.direct;import java.nio.ByteBuffer;import java.util.ArrayList;import java.util.List;/** * 演示直接内存溢出 */public class Demo1_10 {static int _100Mb = 1024 * 1024 * 100;public static void main(String[] args) {List<ByteBuffer> list = new ArrayList<>();int i = 0;try {while (true) {// 这里设置一个大小为100mb的直接内存ByteBuffer byteBuffer = ByteBuffer.allocateDirect(_100Mb);list.add(byteBuffer);i++;}} finally {System.out.println(i);}// 方法区是jvm规范,jdk6 中对方法区的实现称为永久代//jdk8 对方法区的实现称为元空间}}直接内存释放原理我们目前所使用的直接内存是DirectMemory:
package cn.itcast.jvm.t1.direct;import java.io.IOException;import java.nio.ByteBuffer;/** * 我们查看内存管理需要到任务管理器里查看 , 因为该内存属于系统内存,不再属于jvm */public class Demo1_26 {static int _1Gb = 1024 * 1024 * 1024;// 我们使用debug模式调试public static void main(String[] args) throws IOException {// 我们使用byteBuffer来调取1G的内存使用// 我们开启项目后会看到一个内存为1G的java项目ByteBuffer byteBuffer = ByteBuffer.allocateDirect(_1Gb);System.out.println("分配完毕...");// 输入空格后开始进行系统的垃圾回收,这时byteBuffer被回收,我们会注意到内存为1G的项目结束System.in.read();System.out.println("开始释放...");byteBuffer = null;System.gc();System.in.read();}}但是我们需要注意的是我们的jvm的回收功能对系统内存是没有管辖权力的
所以回收ByteBuffer的类另有他人:
package cn.itcast.jvm.t1.direct;import sun.misc.Unsafe;import java.io.IOException;import java.lang.reflect.Field;/** * 直接内存分配的底层原理:Unsafe */public class Demo1_27 {static int _1Gb = 1024 * 1024 * 1024;public static void main(String[] args) throws IOException {// unsafe正常情况下不会使用,因为系统内存通常由系统自动控制 , 我们这里采用暴力反射获取Unsafe unsafe = getUnsafe();// 分配内存(base实际上是该内存的地址,所以我们在释放时同样提供该base地址)long base = unsafe.allocateMemory(_1Gb);unsafe.setMemory(base, _1Gb, (byte) 0);System.in.read();// 释放内存unsafe.freeMemory(base);System.in.read();}// 暴力反射获得unsafe对象public static Unsafe getUnsafe() {try {Field f = Unsafe.class.getDeclaredField("theUnsafe");f.setAccessible(true);Unsafe unsafe = (Unsafe) f.get(null);return unsafe;} catch (NoSuchFieldException | IllegalAccessException e) {throw new RuntimeException(e);}}}然后我们就可以通过DirectMemory的源码来查看为什么它会收到jvm控制:
// Primary constructor// DirectByteBuffer构造器里面直接调用了unsafe类来进行直接内存的控制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));Bits.reserveMemory(size, cap);// 进行直接内存的生产long base = 0;try {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会自动检测directMemory是否还存在,若不存在调用该方法// 这里采用cleaner,直接创建一个新的类型的Deallocator,跳转到下面的类中cleaner = Cleaner.create(this, new Deallocator(base, size, cap));att = null;} // cleaner操作跳转的类,继承了Runnableprivate static class Deallocatorimplements Runnable{private static Unsafe unsafe = Unsafe.getUnsafe();private long address;private long size;private int capacity;private Deallocator(long address, long size, int capacity) {assert (address != 0);this.address = address;this.size = size;this.capacity = capacity;}// 被清理时调用下述方法,采用unsafe.freeMemory(address)来清理直接内存,所以我们的垃圾回收才能清理直接内存public void run() {if (address == 0) {// Paranoiareturn;}unsafe.freeMemory(address);address = 0;Bits.unreserveMemory(size, capacity);}}

推荐阅读