循序渐进理解Java直接内存回收_迷夏的博客-程序员秘密

1.堆外内存定义:

    堆外内存是相对于堆内内存的一个概念,堆内内存是由JVM所掌控的Java进程内存,我们平时在Java中创建的对象都处于堆内内存中,并且他们遵 循JVM的内存管理机制,JVM会采用垃圾回收机制统一管理它们的内存,那么堆外内存就是存在于JVM管控之外的一块内存区域,因此,它不直接虚拟机被管控。

核心API学习:
public class DirectByteBufferTest1 {

    public static void main(String[] args) {
        testRemaing();
    }

    /**
     * Position的位置是插入数据的当前位置,如果插入数据,就会自动后移.
     *
     * 也就是说,如果存储的是两个字节的数据,position的位置是在第三个字节上,下标就是2。
     */
    public static void testPosition(){
        ByteBuffer byteBuffer = ByteBuffer.allocateDirect(1024);
        byteBuffer.putChar('a');
        System.out.println(byteBuffer);
        byteBuffer.putChar('b');
        System.out.println(byteBuffer);
        byteBuffer.putInt(10);
        System.out.println(byteBuffer);
    }

    /**
     * capacity是当前申请的直接内存的容量,它是申请后就不会改变的。
     */
    public static void testPosition1(){
        ByteBuffer byteBuffer = ByteBuffer.allocateDirect(1024);
        System.out.println("capacity:" + byteBuffer.capacity());
    }

    /**
     * 改变这段直接内存的大小,注意limit要比mark和position大,比capacity小.
     */
    public static void testLimit(){
        ByteBuffer byteBuffer = ByteBuffer.allocateDirect(1024);
        System.out.println(byteBuffer.limit());
        byteBuffer.limit(1000);
        System.out.println(byteBuffer.limit());
    }

    /**
     *  mark,就是一个标记为而已,记录当前的position的值。常用的场景,就是记录某一次插入数据的位置,方便下一次进行回溯。
     */
    public static void testMark(){
        ByteBuffer buffer = ByteBuffer.allocateDirect(1024);
        buffer.putChar('a');
        buffer.putChar('c');
        System.out.println("插入完数据 " + buffer);
        // 记录mark的位置
        buffer.mark();
        // 设置的position一定要比mark大,否则mark无法重置
        buffer.position(30);
        System.out.println("reset前 " + buffer);
        // 重置reset ,reset后的position=mark
        buffer.reset();
        System.out.println("reset后 " + buffer);
        //清除标记,position变成0,mark变成-1
        buffer.rewind();
        System.out.println("清除标记后 " + buffer);
    }

    /**
     * remaing则表示当前的剩余空间
     */
    public static void testRemaing(){
        ByteBuffer buffer = ByteBuffer.allocateDirect(1024);
        System.out.println(buffer.remaining());
    }
    
    /**
     * clear清除mark和position,还有limit的位置:
     */
    public static void testClear(){
        ByteBuffer buffer = ByteBuffer.allocateDirect(1024);
        buffer.clear();
        System.out.println(buffer);
    }

    /**
     * flip:改变当前的Position为limit,主要是用于读取操作.
     */
    public static void testFlip(){
        ByteBuffer buffer = ByteBuffer.allocateDirect(1024);
        buffer.flip();
        System.out.println(buffer.limit());
        System.out.println(buffer.position());
    }

    /**
     * compact:这个方法在读取一部分数据的时候比较常用。
     *
     * 它会把当前的Position移到0,然后position+1移到1。
     */
    public static void testCompact(){
        ByteBuffer buffer = ByteBuffer.allocateDirect(10);
        byte[] data = {1,2,3,4,5,6,7,8};
        buffer.put(data);
        System.out.println("写byte[]后 " + buffer);
        buffer.compact();
        System.out.println("compact 后:" + buffer);
        System.out.println(buffer.get(1));
    }

    /**
     * isDirect()
     *
     * 这个方法用于判断是否是直接内存。如果是返回true,如果不是返回false。
     */
    public static void testIsDirect(){
        ByteBuffer buffer = ByteBuffer.allocateDirect(10);
        System.out.println(buffer.isDirect());
    }
    }

2. 直接内存读写实现

public class DirectByteBufferTest2 {

    public static void main(String[] args) {
        readFunction1();
    }

    /**
     * 写操作 - 1
     */
    public static void writeFunction1(){
        ByteBuffer buffer = ByteBuffer.allocateDirect(10);
        byte[] data = {1,2};
        buffer.put(data);
        System.out.println("写byte[]后 " + buffer);
    }

    /**
     * 写操作 - 2
     */
    public static void writeFunction2(){
        ByteBuffer buffer = ByteBuffer.allocateDirect(10);
        byte[] data = {1,2};
        buffer.put(data);
        System.out.println("写byte[]后 " + buffer);
        buffer.clear();
        buffer.put("hello".getBytes());
        System.out.println("写string后 " + buffer);
    }

    /**
     * 写操作 - 3
     */
    public static void writeFunction3(){
        ByteBuffer buffer = ByteBuffer.allocateDirect(1024);
        byte[] data = {1,2};
        buffer.put(data);
        System.out.println("写byte[]后 " + buffer);
        buffer.flip();
        System.out.println("");
        buffer.put("hello".getBytes());
        System.out.println("写string后 " + buffer);
    }

    /**
     * 读操作 - 1
     */
    public static void readFunction1() {
        ByteBuffer buffer = ByteBuffer.allocateDirect(10);
        buffer.put(new byte[]{1, 2, 3, 4});
        System.out.println("刚写完数据 " + buffer);
        buffer.flip();
        System.out.println("flip之后 " + buffer);
        byte[] target = new byte[buffer.limit()];
        buffer.get(target);
        //自动读取target.length个数据
        for (byte b : target) {
            System.out.print(b + " ");
        }
    }

}

3.直接内存内存回收原理以及源码分析

  1.作用链
  直接内存作用链:本地I/O -- 直接内存 -- 本地I/O
  堆内内存作用链:本地I/O -- 直接内存 -- 堆内内存 -- 本地I/O
  2.注意事项
  DirectBuffer并没有真正的向操作系统分配内存,其最终还是通过调用Unsafe的allocateMemory()来进行内存分配。不过JVM对    DirectMemory可申请的大小也有限制,可用-XX:MaxDirectMemorySize=1M设置,这部分内存不受JVM垃圾回收管理。
  3.内存分配

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类申请额度,Bits类内部维护着当前已经使用的堆外内存值,
        会check是当前申请的大小与已经使用的内存大小是否超过总的堆外内存大小
        (默认大小与堆内存一样),可以使用 -XX:MaxDirectMemorySize 重新设置。
        如果check不通过,会主动执行System.gc(),然后sleep 100ms,再进行check,
        如果内存还是不足,就抛出OOM Error。
    
    */
    Bits.reserveMemory(size, cap);

    long base = 0;
    try {
        // 如果check通过,就会调用unsafe.allocateMemory真正分配内存,返回内存地址
        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 boundary
        address = base + ps - (base & (ps - 1));
    } else {
        address = base;
    }
    cleaner = Cleaner.create(this, new Deallocator(base, size, cap));
    att = null;



}

4.内存回收原理

Cleaner类,内部维护了一个Cleaner对象的链表,通过create(Object, Runnable)方法创建cleaner对象,调用自身的add方法,将其加入到链表中。
更重要的是提供了clean方法,clean方法首先将对象自身从链表中删除,保证只调用一次,然后执行this.thunk的run方法,thunk就是由创建时传入的Runnable参数,也就是说clean只负责触发Runnable的run方法,至于Runnable做什么任务它不关心。那DirectByteBuffer传进来的Runnable是什么呢?

private static class Deallocator
    implements 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;
    }

    public void run() {
        if (address == 0) {
            // Paranoia
            return;
        }
        unsafe.freeMemory(address);
        address = 0;
        Bits.unreserveMemory(size, capacity);
    }

}

5.自动回收

自动回收,当然由GC负责回收的,在GC时会扫描DirectByteBuffer对象否有是影响GC的引用,如没有,在回收DirectByteBuffer对象的同时且会回收其占用的堆外内存。但是JVM如何释放其占用的堆外内存呢?如何跟Cleaner关联起来呢? 这得从Cleaner继承了PhantomReference引用说起。说到Reference,还有SoftReference、WeakReference、FinalReference他们作用各不相同,这里就不展开说了。 简单介绍PhantomReference(虚引用),首先虚引用不会影响JVM是否要GC这个对象的判断,当GC某个对象时,如果有此对象上还有虚引用,会将PhantomReference对象插入ReferenceQueue队列。 PhantomReference插入到哪个队列呢? 看PhantomReference类代码,其继承自Reference,Reference对象有个ReferenceQueue成员,这个也就是PhantomReference对象插入的ReferenceQueue队列,此成员如果不由外部传入就是ReferenceQueue.NULL。如果需要通过queue拿到PhantomReference对象,这个ReferenceQueue对象还是必须由外部传入。 Reference 类内部static静态块会启动ReferenceHandler线程,线程优先级很高,这个线程是用来处理JVM在gc过程中交接过来的reference,并做一些处理。 我们来看看ReferenceHandler是如何处理的?直接看run方法,首先是个死循环,一直在那不停的干活,synchronized块内的这段主要是交接JVM扔过来的reference(就是pending),再往下看,很明显,调用了cleaner的clean方法。调完之后直接continue结束此次循环,这个reference并没有进入queue,也就是说Cleaner虚引用是不放入ReferenceQueue。

6.手动回收

手动回收,就是由开发手动调用DirectByteBuffer的cleaner的clean方法来释放空间。由于cleaner是private反问权限,所以自然想到使用反射来实现。

public static void clean(final ByteBuffer byteBuffer) {  

 if (byteBuffer.isDirect()) { 

        Field cleanerField = byteBuffer.getClass().getDeclaredField("cleaner");

        cleanerField.setAccessible(true);

        Cleaner cleaner = (Cleaner) cleanerField.get(byteBuffer);

        cleaner.clean();

    }

}

还有另一种方法,DirectByteBuffer实现了DirectBuffer接口,这个接口有cleaner方法可以获取cleaner对象

public static void clean(final ByteBuffer byteBuffer) {  

    if (byteBuffer.isDirect()) {  

        ((DirectBuffer)byteBuffer).cleaner().clean();  

    }  

}

7.Java直接内存与非直接内存性能测试

申请分配地址速度比较

int time = 10000000;
Date begin = new Date();
for(int i=0;i<time;i++){
    ByteBuffer buffer = ByteBuffer.allocate(2);
}
Date end = new Date();
System.out.println(end.getTime()-begin.getTime());
begin = new Date();
for(int i=0;i<time;i++){
    ByteBuffer buffer = ByteBuffer.allocateDirect(2);
}
end = new Date();
System.out.println(end.getTime()-begin.getTime());

测试结果

读写速度比较

int time = 1000;
Date begin = new Date();
ByteBuffer buffer = ByteBuffer.allocate(2*time);
for(int i=0;i<time;i++){
    buffer.putChar('a');
}
buffer.flip();
for(int i=0;i<time;i++){
    buffer.getChar();
}
Date end = new Date();
System.out.println(end.getTime()-begin.getTime());
begin = new Date();
ByteBuffer buffer2 = ByteBuffer.allocateDirect(2*time);
for(int i=0;i<time;i++){
    buffer2.putChar('a');
}
buffer2.flip();
for(int i=0;i<time;i++){
    buffer2.getChar();
}
end = new Date();
System.out.println(end.getTime()-begin.getTime());

测试结果

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/qq_39767198/article/details/100176252

智能推荐

Ubuntu15.04 libreoffice启动电脑系统重启_JAVApf_cuit的博客-程序员秘密

重启界面出现代码:starting version 219,求解决

秘笈大公开 | 魏泓手把手教你利用无菌动物,提高国自然中标率!_刘永鑫Adam的博客-程序员秘密

本文转自“热心肠先生”,已获授权2019国家自然科学基金的申请即将开始,我们热心肠日报的浏览量和用户平均停留时间也都达到了春节假期后的一个小高峰。今天,我们特别编发陆军军医大学(第三军医...

Git进阶(九):Git 命令实现提交指定文件_git命令指定文件提交_No Silver Bullet的博客-程序员秘密

文章目录一、前言二、操作命令三、拓展阅读一、前言在项目开发过程中,经常会出现这样一种开发场景:本地部分代码还未写完,测试那边跑来告诉你要赶紧修复某个文件bug,情况紧急,待你改完后,提交代码的时候,发现自己还在开发阶段的某些代码不想提交,此时,可利用git指令帮助你实现提交指定文件!二、操作命令git status -s 查看仓库状态;git add src/components/文件名 添加需要提交的文件名(加路径–参考git status 打印的文件路径);git stash -u -

2家上市,4家排队,游戏公司今年为何偏爱港股?_love_hot_girl的博客-程序员秘密

https://zhuanlan.zhihu.com/p/40455192随着指尖悦动、第七大道在同一周内相继上市,本就热闹的港股市场吸引了不少游戏人的关注。7月12日,指尖悦动率先上市,值得一提的是,当天上市的还有另外7家公司,一度出现锣不够用、两人共敲一锣的罕见场面,而和指尖悦动CEO刘杰同敲一锣的,是映客CEO奉佑生;7月18日,第七大道成功上市,作为国内少有的曾尝试登陆美股、A股、...

100%解决ios上audio不能自动播放的问题_weixin_30607659的博客-程序员秘密

由于ios的安全机制问题,不允许audio和video自动播放,所以想要使audio标签自动播放那是实现不了的,即使给play()也是播放不了。解决方法:首先,创建audio标签:&lt;audio id="success_music" src="/success.mp3" preload="auto"&gt;&lt;/audio&gt;,preload为自动加载接下来,创建scrip...

​自动驾驶测试与验证的挑战_智能交通技术的博客-程序员秘密

来源 |鲨鱼观海一篇关于自动驾驶测试的文献翻译。摘 要一般来说软件测试常常只是寻找bug,而不是通过精密的实验来保证产品质量。一个相比简单的系统级的测试,即测试—失败—补丁(改进)—测试...

随便推点

Oracle EBS R12 SLA与GL关系变化_oracle r12 sla_GradyFeng的博客-程序员秘密

Oracle EBS R12 SLA与GL关系变化SLA概念:SLA(Subledger Accounting) 子帐是子分类帐会计的简称,字面上的含义就是子分类帐会计分录SLA&amp;amp;GL关系:   R12功能模块上面都启用了MOAC特性,新增了子模块和税模块,OPM和离散库存也集成了,所有的子模块会计分录都可以使用特定的公司配置出来;之间的关系分位三种:1、子分类帐会计其实就...

Java 运算符_java运算符_Naion的博客-程序员秘密

介绍Java当中的常见运算符。

判断邻接矩阵无向图的连通性函数C语言,判断一个无向图是否为连通图的方法..._辜进喜的博客-程序员秘密

无向图的连通性判断一个无向图是否为连通图。输入为无向图的邻接矩阵。输入输入有若干行第一行为正整数N(0接下来N行,每行有N个数据,每个数据以空格分隔,代表邻接矩阵。输出一行。连通yes, 否则no.测试输入31 1 11 1 11 1 1测试输出yes源代码#include#define N 3002int a[N][N];int main(){int i,j,k,n;scanf("%d\n",&...

zynq 鸿蒙,赛灵思扩展其UltraScale+产品组合:面向人工智能、边缘计算_魔鬼在尖叫的博客-程序员秘密

【TechWeb】3月17日消息,自适应计算领先企业赛灵思(Xilinx)今日宣布面向市场扩展其 UltraScale+ 产品组合,以支持需要超紧凑及智能边缘解决方案的新型应用。新款 Artix 和 Zynq UltraScale+ 器件的外形尺寸较传统芯片封装缩小了70%,能够满足工业、视觉、医疗、广播、消费、汽车和互联市场等更广泛的应用领域。作为全球唯一基于 16 纳米技术的硬件灵活应变成本优...

Window10 VBS调用文件浏览对话框_lq0954的博客-程序员秘密

 Msgbox CreateObject(&quot;WScript.Shell&quot;).Exec(&quot;mshta vbscript:&quot;&quot;&amp;lt;input type=file id=f&amp;gt;&amp;lt;script&amp;gt;f.click();new ActiveXObject('Scripting.FileSystemObject').GetStandardStream(1).Write(f.value)[c...

STM32编写OLED显示屏驱动_oled驱动_花落已飘的博客-程序员秘密

这篇文章将带大家学习如何编写OLED显示屏的驱动程序。这里我使用的是HAL库的硬件IIC,OLED屏幕使用的是SSD1306的。这里需要参考SSD1306的数据手册来编写驱动程序。这篇文章主要就是讲解了如何编写OLED的驱动程序。

推荐文章

热门文章

相关标签