java之HeapByteBuffer&DirectByteBuffer以及回收DirectByteBuffer

byte buffer一般在网络交互过程中java使用得比较多,尤其是以NIO的框架中;

看名字就知道是以字节码作为缓冲的,先buffer一段,然后flush到终端。

而本文要说的一个重点就是HeapByteBuffer与DirectByteBuffer,以及如何合理使用DirectByteBuffer。

1、HeapByteBufferDirectByteBuffer,在原理上,前者可以看出分配的buffer是在heap区域的,其实真正flush到远程的时候会先拷贝得到直接内存,再做下一步操作(考虑细节还会到OS级别的内核区直接内存),其实发送静态文件最快速的方法是通过OS级别的send_file,只会经过OS一个内核拷贝,而不会来回拷贝;在NIO的框架下,很多框架会采用DirectByteBuffer来操作,这样分配的内存不再是在java
heap
上,而是在C heap上,经过性能测试,可以得到非常快速的网络交互,在大量的网络交互下,一般速度会比HeapByteBuffer要快速好几倍。

最基本的情况下

分配HeapByteBuffer的方法是:

ByteBuffer.allocate(int capacity);参数大小为字节的数量

分配DirectByteBuffer的方法是:

ByteBuffer.allocateDirect(int capacity);//可以看到分配内存是通过unsafe.allocateMemory()来实现的,这个unsafe默认情况下java代码是没有能力可以调用到的,不过你可以通过反射的手段得到实例进而做操作,当然你需要保证的是程序的稳定性,既然叫unsafe的,就是告诉你这不是安全的,其实并不是不安全,而是交给程序员来操作,它可能会因为程序员的能力而导致不安全,而并非它本身不安全。

由于HeapByteBufferDirectByteBuffer类都是default类型的,所以你无法字节访问到,你只能通过ByteBuffer间接访问到它,因为JVM不想让你访问到它,对了,JVM不想让你访问到它肯定就有它不可告人的秘密;后面我们来跟踪下他的秘密吧。

2、前面说到了,这块区域不是在java heap上,那么这块内存的大小是多少呢?默认是一般是64M,可以通过参数:-XX:MaxDirectMemorySize来控制,你够牛的话,还可以用代码控制,呵呵,这里就不多说了。

3、直接内存好,我们为啥不都用直接内存?请注意,这个直接内存的释放并不是由你控制的,而是由full gc来控制的,直接内存会自己检测情况而调用system.gc(),但是如果参数中使用了DisableExplicitGC 那么这是个坑了,所以啊,这玩意,设置不设置都是一个坑坑,所以java的优化有没有绝对的,只有针对实际情况的,针对实际情况需要对系统做一些拆分做不同的优化。

4、那么full
gc不触发,我想自己释放这部分内存有方法吗?可以的,在这里没有什么是不可以的,呵呵!私有属性我们都任意玩他,还有什么不可以玩的;我们看看它的源码中DirectByteBuffer发现有一个:Cleaner,貌似是用来搞资源回收的,经过查证,的确是,而且又看到这个对象是sun.misc开头的了,此时既惊喜又郁闷,呵呵,只要我能拿到它,我就能有希望消灭掉了;下面第五步我们来做个试验。

5、因为我们的代码全是私有的,所以我要访问它不能直接访问,我需要通过反射来实现,OK,我知道要调用cleaner()方法来获取它Cleaner对象,进而通过该对象,执行clean方法;(付:以下代码大部分也取自网络上的一篇copy无数次的代码,但是那个代码是有问题的,有问题的部分,我将用红色标识出来,如果没有哪条代码是无法运行的

import java.nio.ByteBuffer;
import sun.nio.ch.DirectBuffer;

public class DirectByteBufferCleaner {

        public static void clean(final ByteBuffer byteBuffer) {
              if (byteBuffer.isDirect()) {
                 ((DirectBuffer)byteBuffer).cleaner().clean();
              }
        }
}

上述类你可以在任何位置建立都可以,这里多谢一楼的回复,以前我的写法是见到DirectByteBuffer类是Default类型的,因此这个类无法直接引用到,是通过反射去找到cleaner的实例,进而调用内部的clean方法,那样做麻烦了,其实并不需要那么麻烦,因为DirectByteBuffer
implements了DirectBuffer,而DirectBuffer本身是public的,所以通过接口去调用内部的Clear对象来做clean方法。

我们下面来做测试来证明这个程序是有效地回收的:

在任意一个地方写一段main方法来调用,我这里就直接写在这个类里面了:

public static void sleep(long i) {
    try {
          Thread.sleep(i);
     }catch(Exception e) {
          /*skip*/
     }
}
public static void main(String []args) throws Exception {
       ByteBuffer buffer = ByteBuffer.allocateDirect(1024 * 1024 * 100);
       System.out.println("start");
       sleep(10000);
       clean(buffer);
       System.out.println("end");
       sleep(10000);
}

这里分配了100M内存,为了将结果看清楚,在执行前,执行后分别看看延迟10s,当然你可以根据你的要求自己改改。请提前将OS的资源管理器打开,看看当前使用的内存是多少,如果你是linux当然是看看free或者用top等命令来看;本地程序我是用windows完成,在运行前机器的内存如下图所示:

开始运行在输入start后,但是未输出end前,内存直接上升将近100m。

在输入end后发现内存立即降低到2.47m,说明回收是有效的。

此时可以观察JVM堆的内存,不会有太多的变化,注意:JVM本身启动后也有一些内存开销,所以不要将那个开销和这个绑定在一起;这里之所以一次性申请100m也是为了看清楚过程,其余的可以做实验玩玩了。

时间: 2024-10-01 06:37:53

java之HeapByteBuffer&DirectByteBuffer以及回收DirectByteBuffer的相关文章

Java语言灵巧指针与垃圾回收

在JAVA 和 C# 中都有垃圾回收功能,程序员在分配一段内存后可以不再理会,而由垃圾回收自动回收,从而使程序员从复杂的内存管理中解脱出来.这是JAVA 和 C#的一大优点.而C++程序员在用 new 分配了一段内存后,还必须用 delete 释放,否则将造成资源泄漏.因此,一些C++ 书上经常告诫程序员:要养成好的习惯,new 与 delete 要成对出现,时刻记住将内存释放回系统.但是,事情只是这么简单吗? 经常地,在使用C++的过程中,我们会遇到下面的情形: class A { publi

探秘Java虚拟机——内存管理与垃圾回收

1.Java虚拟机运行时的数据区 2.常用的内存区域调节参数 -Xms:初始堆大小,默认为物理内存的1/64(<1GB):默认(MinHeapFreeRatio参数可以调整)空余堆内存小于40%时,JVM就会增大堆直到-Xmx的最大限制 -Xmx:最大堆大小,默认(MaxHeapFreeRatio参数可以调整)空余堆内存大于70%时,JVM会减少堆直到 -Xms的最小限制 -Xmn:新生代的内存空间大小,注意:此处的大小是(eden+ 2 survivor space).与jmap -heap中

探秘Java虚拟机——内存管理与垃圾回收(转)

本文主要是基于Sun JDK 1.6 Garbage Collector(作者:毕玄)的整理与总结,原文请读者在网上搜索. 1.Java虚拟机运行时的数据区 2.常用的内存区域调节参数 -Xms:初始堆大小,默认为物理内存的1/64(<1GB):默认(MinHeapFreeRatio参数可以调整)空余堆内存小于40%时,JVM就会增大堆直到-Xmx的最大限制 -Xmx:最大堆大小,默认(MaxHeapFreeRatio参数可以调整)空余堆内存大于70%时,JVM会减少堆直到 -Xms的最小限制

Java文件流关闭和垃圾回收机制_java

1.先看以下一段代码 import java.io.FileInputStream; public class TTT { public static void main(String[] args) throws Exception { for (int i = 0; i < 10; i++) { final String threadId = "thread_" + i; Thread thread = new Thread(new Runnable() { public v

java类的问题-静态变量的有关问题,垃圾回收和静态变量

问题描述 静态变量的有关问题,垃圾回收和静态变量 1.java在内存紧缺时候会回收掉不用的静态变量的内存吗? 2.一个类中含有对静态变量的引用,当这个类不使用的时候会被垃圾回收吗? 解决方案 1.不会 2.不会

认识JVM--第一篇-对象分配&amp;amp;回收算法

本来标题党想写成<深入JVM>,不过不太敢写,我想一小篇博客我想还不足以说明JVM,在本文中,会就我所知给大家介绍JVM的很多内部知识,概念会相对较粗,因为太细的内容要写,这里肯定写不出来:本文主要偏重理论,没有什么实践,中间除一些官方资料外,还有部分自身的理解,所以请大家不要完全信任本文内容:另外本文会有一小部分纠正以前一篇文章对于intern()使用方法的错误,本文会在其中说明使用错误的原因,大致文章内容有以下几个部分:   1.JVM虚拟内存组成及操作系统地址表 2.新生成对象在Heap

Java核心技术 卷Ⅰ 基础知识(原书第10版)

Java核心技术系列 Java核心技术 卷Ⅰ 基础知识 (原书第10版) Core Java Volume I-Fundamentals (10th Edition) [美] 凯S.霍斯特曼(Cay S. Horstmann) 著 周立新 陈 波 叶乃文 邝劲筠 杜永萍 译 图书在版编目(CIP)数据 Java核心技术 卷Ⅰ 基础知识(原书第10版) / (美)凯S. 霍斯特曼(Cay S. Horstmann)著:周立新等译. -北京:机械工业出版社,2016.8 (Java核心技术系列) 书

模块化Java:动态模块化

在前一篇文章<模块化Java:静态模块化>中,我们讨论了如何构建Java模 块并将其作为一个单独的JAR进行部署.文中的例子给出了一个client和一个 server bundle(两者在同一个VM中),client通过工厂方法找到server.在该 例子中,工厂实例化了一个已知类,当然也可以使用反射来获取一个服务实现: Spring就是大量运用这种技术把spring对象绑定在一起的. 在我们讨论动态服务之前,有必要回顾一下类路径,因为标准Java代码和模 块化Java代码的区别之一就是依赖在

收集Java面试题知识点(Java基础部分二)

1.用最有效率的方法算出2乘以8等于几? 2 << 3 因为将一个数左移n位,就相当于乘以了2的n次方,那么,一个数乘以8只要将其左移3位即可,而位运算CPU直接支持的,效率最高,所以,2乘以8等于几的最效率的方法是2 << 3. 但需要注意的是,如果这个数字本身已经很大,比如本身已经是2的30次方了,此时再用这种位移运算就可能导致"溢出",这样就得不到正确结果了. 2.使用final关键字修饰一个变量时,是引用不能变,还是引用的对象不能变? 使用final关键