我之前的一篇博客: java中使用堆外内存,关于内存回收需要注意的事和没有解决的遗留问题(等大神解答)  介绍了java堆外内存的使用,以及堆外内存的释放。那篇博客遗留了一个问题:DirectByteBuffer究竟是如何释放堆外内存的?本文主要是解决下那篇博客的遗留问题。

首先我们修改下JVM的启动参数,重新运行之前博客中的代码。JVM启动参数和测试代码如下:

-verbose:gc -XX:+PrintGCDetails -XX:+DisableExplicitGC -XX:MaxDirectMemorySize=40M
import java.nio.ByteBuffer;
 
public class TestDirectByteBuffer
{
    // -verbose:gc -XX:+PrintGCDetails -XX:MaxDirectMemorySize=40M
    // 加上-XX:+DisableExplicitGC,也会报OOM(Direct buffer memory)
    public static void main(String[] args) throws Exception
    {
        while (true)
        {
            ByteBuffer.allocateDirect(10 * 1024 * 1024);
        }
    }
}
与之前的JVM启动参数相比,增加了-XX:+DisableExplicitGC,这个参数作用是禁止代码中显示调用GC。代码如何显示调用GC呢,通过System.gc()函数调用。如果加上了这个JVM启动参数,那么代码中调用System.gc()没有任何效果,相当于是没有这行代码一样。

Exception in thread "main" java.lang.OutOfMemoryError: Direct buffer memory
    at java.nio.Bits.reserveMemory(Bits.java:632)
    at java.nio.DirectByteBuffer.<init>(DirectByteBuffer.java:97)
    at java.nio.ByteBuffer.allocateDirect(ByteBuffer.java:288)
    at direct.TestDirectByteBuffer.main(TestDirectByteBuffer.java:13)
Heap
 PSYoungGen      total 9536K, used 507K [0x1cf90000, 0x1da30000, 0x27a30000)
  eden space 8192K, 6% used [0x1cf90000,0x1d00ef30,0x1d790000)
  from space 1344K, 0% used [0x1d8e0000,0x1d8e0000,0x1da30000)
  to   space 1344K, 0% used [0x1d790000,0x1d790000,0x1d8e0000)
 PSOldGen        total 21888K, used 0K [0x07a30000, 0x08f90000, 0x1cf90000)
  object space 21888K, 0% used [0x07a30000,0x07a30000,0x08f90000)
 PSPermGen       total 16384K, used 2292K [0x03a30000, 0x04a30000, 0x07a30000)
  object space 16384K, 13% used [0x03a30000,0x03c6d380,0x04a30000)
显然堆内存(包括新生代和老年代)内存很充足,但是堆外内存溢出了。也就是说NIO直接内存的回收,需要依赖于System.gc()。如果我们的应用中使用了java nio中的direct memory,那么使用-XX:+DisableExplicitGC一定要小心,存在潜在的内存泄露风险。

我们知道java代码无法强制JVM何时进行垃圾回收,也就是说垃圾回收这个动作的触发,完全由JVM自己控制,它会挑选合适的时机回收堆内存中的无用java对象。代码中显示调用System.gc(),只是建议JVM进行垃圾回收,但是到底会不会执行垃圾回收是不确定的,可能会进行垃圾回收,也可能不会。什么时候才是合适的时机呢?一般来说是,系统比较空闲的时候(比如JVM中活动的线程很少的时候),还有就是内存不足,不得不进行垃圾回收。我们例子中的根本矛盾在于:堆内存由JVM自己管理,堆外内存必须要由我们自己释放;堆内存的消耗速度远远小于堆外内存的消耗,但要命的是必须先释放堆内存中的对象,才能释放堆外内存,但是我们又不能强制JVM释放堆内存。

下面我们看下new DirectByteBuffer的源码

DirectByteBuffer(int cap) 
{            
 
    super(-1, 0, cap, cap, false);
    Bits.reserveMemory(cap);
    int ps = Bits.pageSize();
    long base = 0;
    try {
        base = unsafe.allocateMemory(cap + ps);
    } catch (OutOfMemoryError x) {
        Bits.unreserveMemory(cap);
        throw x;
    }
    unsafe.setMemory(base, cap + ps, (byte) 0);
    if (base % ps != 0) {
        // Round up to page boundary
        address = base + ps - (base & (ps - 1));
    } else {
        address = base;
    }
    cleaner = Cleaner.create(this, new Deallocator(base, cap));
        viewedBuffer = null;
}
static void reserveMemory(long size) 
{
 
    synchronized (Bits.class) {
        if (!memoryLimitSet && VM.isBooted()) {
        maxMemory = VM.maxDirectMemory();
        memoryLimitSet = true;
        }
        if (size <= maxMemory - reservedMemory) {
        reservedMemory += size;
        return;
        }
    }
    
    // 显示调用垃圾回收
    System.gc();
    try {
        Thread.sleep(100);
    } catch (InterruptedException x) {
        // Restore interrupt status
        Thread.currentThread().interrupt();
    }
    synchronized (Bits.class) {
        if (reservedMemory + size > maxMemory)
        throw new OutOfMemoryError("Direct buffer memory");
        reservedMemory += size;
    }
 
}
可以看到:每次执行代码ByteBuffer.allocateDirect(10 * 1024 * 1024);的时候,都会调用一次System.gc()。目的很简单,就是希望JVM赶紧把堆中的无用对象回收掉。虽然System.gc()只是建议JVM进行垃圾回收,不能强制。可以这里理解:如此频繁的建议JVM进行垃圾回收,就算堆内存还很充足,JVM也不能对我们显示的GC视而不见啊。所以显示的使用System.gc(),还是有用的。也就是说direct memory的释放,依赖于System.gc()触发JVM的垃圾回收动作,只有回收了堆内存中的DirectByteBuffer对象,才有可能回收DirectByteBuffer对象中占用的堆外内存空间。

回想下java中使用堆外内存,关于内存回收需要注意的事和没有解决的遗留问题 这篇博客中的第4节 正确释放Unsafe分配的堆外内存

我们在RevisedObjectInHeap类中

// 让对象占用堆内存,触发[Full GC  
private byte[] bytes = null;
 
public RevisedObjectInHeap()  
{  
    address = unsafe.allocateMemory(2 * 1024 * 1024);  
    
    // 占用堆内存
    bytes = new byte[1024 * 1024];  

定义了1M的字节数组,就是为了让JVM赶紧进行垃圾回收,这样当堆内存中的垃圾对象被回收的时候,JVM就能够调用finalize()方法,就能够释放堆外内存。这跟NIO类库中,显示调用System.gc()目的是一样的。至此我们可以得出:堆内存和非堆内存资源(文件句柄、socket句柄,堆外内存、数据库连接等)的同步释放,的确是一个很棘手的问题。虽然通过System.gc()能够避免内存泄露,但是严重影响系统的运行效率,因为垃圾回收会减慢系统的运行。最佳编程实践是:暴露出释放资源的接口,程序员使用完成后,显示释放,这样就能够避免堆内存和非堆内存资源的同步释放的难题。

RevisedObjectInHeap类中通过finalize()方法来释放堆外内存的,阅读源码可以发现,NIO中direct memory的释放并不是通过finalize(),而是通过sun.misc.Cleaner实现的

cleaner = Cleaner.create(this, new Deallocator(base, cap));

为什么不用finalize呢?因为finalize不安全,也非常影响性能。什么是sun.misc.Cleaner?这是个幽灵引用PhantomReference。后续博客将继续分析finalize和Cleaner等垃圾回收相关的知识,欢迎关注。
--------------------- 
作者:aitangyong 
来源:CSDN 
原文:https://blog.csdn.net/aitangyong/article/details/39403031 
版权声明:本文为博主原创文章,转载请附上博文链接!

system.gc()和-XX:+DisableExplicitGC启动参数,以及DirectByteBuffer的内存释放相关推荐

  1. JVM 启动参数详解:博观而约取、厚积而薄发

    JVM 作为一个通用的虚拟机,我们可以通过启动 Java 命令时指定不同的 JVM 参数,让 JVM 调整自己的运行状态和行为,内存管理和垃圾回收的 GC 算法,添加和处理调试和诊断信息等等.本节概括 ...

  2. JVM学习笔记之-垃圾回收相关概念 System.gc()的理解 内存溢出与内存泄漏 STW 垃圾回收的并行与并发 安全点与安全区域 再谈引用:强引用 软引用 弱引用 虚引用 终结器引用

    System.gc()的理解 在默认情况下,通过System.gc()或者Runtime. getRuntime ( ).gc ()的调用,会显式触发Full GC,同时对老年代和新生代进行回收,尝试 ...

  3. JVM 启动参数详解

    JVM 启动参数详解 JVM 作为一个通用的虚拟机,我们可以通过启动Java命令时指定不同JVM参数,让 JVM调整自己的运行状态和行为,内存管理和垃圾回收的GC算法等等. 直接通过命令行启动 Jav ...

  4. Java启动参数(-, -X, -XX参数)详解

    目录 Java启动参数分类 一.JVM标准参数(-) 获取方法: java -help 二.JVM非标准参数(-X) 获取方法: java -X 三.JVM非Stable参数(-XX) 获取方法:   ...

  5. JVM 启动参数规则:-、-X、-XX、-D表示什么意思?

    通过JVM运行一个java程序的时候,我们通常会用java -jar xxxx.jar 的命令,我们通常都会根据情况配置很多的运行参数. 例如: /usr/local/java/jdk1.8.0_13 ...

  6. tomcat 启动参数 Xms, Xmx, XX:MaxNewSize, XX:PermSize, -XX:MaxPermSize, Djava.awt.headless

    在 tomcat/bin/catalina.sh 的 第一行#!/bin/sh 下添加 JAVA_OPTS="-server -Xms512m -Xmx1024m -XX:MaxNewSiz ...

  7. 不需要再手工指定JVM启动参数-XX:+UseCompressedOops

    技术团队通过 GCeasy 工具分析完几千次用户上传的GC日志后, 发现一个现象: 仍然有很多Java程序传入了JVM启动参数 -XX:+UseCompressedOops. 实际上,如果JVM的版本 ...

  8. java verbose gc_jvm参数-verbose:gc和-XX:+PrintGC有区别?

    jvm调优,参数-verbose:gc和-XX:+PrintGC有什么具体的区别?还是说效果一样的,打印下了没发现什么差别. 参数1: -XX:+PrintGC -XX:+PrintGCDetails ...

  9. 使用-XX:+PrintCommandLineFlags展示JVM启动参数

    导语: 近期在学习周志明老师的<<深入理解Java虚拟机: JVM高级特性与最佳实践(第3版)>>, 在IDEA中测试老师的示例代码时, 发现程序的输出和老师的输出不一样, 想 ...

  10. jvm 参数 -verbose:gc 和 -XX:+PrintGC 的区别?

    文章目录 一.日志内容 1.1. `-verbose:gc` 参数: 1.2. `-XX:+PrintGC` 参数: 二.官方说明 2.1.`-verbose:gc` 是 稳定版本 2.2.`-XX: ...

最新文章

  1. 基于事件通信的轻量级MVP框架实现,附源码
  2. Kinect学习(四):提取深度数据
  3. linux 线程pthread_detach,linux线程之pthread_join和pthread_detach
  4. Eclipse自动生成返回值对象与补全与加注释
  5. 前端学习(3025):vue+element今日头条管理-侧边导航菜单
  6. 不愿意和别人打交道_始终和人保持距离,最不合群的3星座,孤僻却有真本事,能成大事...
  7. Win 10 UEFI + Ubuntu 18.04 UEFI 双系统 (by quqi99)
  8. 关于4442卡的读密码问题
  9. SPSS 随机区组秩和检验
  10. 锦绣金秋,共荟未来——荟语酒店投资人沙龙活动
  11. 关于连接同一wifi,手机可以上网,电脑无法上网问题
  12. Ubuntu中编译linux内核后使用make menuconfig报错的解决办法
  13. 递归算法时间复杂度的数学证明过程(主定理)
  14. webpack出现CssSyntaxError
  15. 千百万Java开发者的福音:跨平台Cocos2d-Java游戏引擎诞生
  16. 弘辽科技:拼多多店铺账号忘了怎么找回?
  17. 计算机网络——物理层(宽带接入技术)
  18. 在VMWare虚拟机上安装CentOS 7
  19. Linux文件与目录的复制、删除与移动(cp、rm、mv)
  20. 怎么调整计算机的音量,教你电脑声音如何调大

热门文章

  1. CAD图纸打印自动排版
  2. ADF实现主副控件联动的方法
  3. ue4蓝图运行顺序_UE4蓝图编程的第一步
  4. js实现签名功能(vue中使用电子签名)
  5. 各种缩写名词汇总,力求最全面最精确
  6. vivado实现cameralink接口
  7. php7 opcache 编译,PHP7中用opcache.file_cache导出脚本opcode实现源代码保护
  8. 推荐几款实用性强的外业勘察地图软件
  9. 别老盯着5G,这些物联网应用用4G LTE技术足够了
  10. c语言入门ppt演示,c语言学习ppt-C语言入门教程ppt(共434页)免费版【精品课件】-东坡下载...