首先我们修改下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相关 - 深入理解 System.gc()

    本文基于 Java 17-ea,但是相关设计在 Java 11 之后是大致一样的 我们经常在面试中询问 System.gc() 究竟会不会立刻触发 Full GC,网上也有很多人给出了答案,但是这些答 ...

  2. JVM启动参数详解

    JVM启动参数以及具体的解释: -Xmx1024M 最大堆内存 -Xms1024M 初始化堆内存,正常和最大堆内存相同,减少动态改变的内存损耗 -Xmn384M 年轻代内存 -XX:PermSize= ...

  3. 【JVM】jvm启动参数

    -server -Xmx2048m -Xms1500m -Xmn1024m -XX:PermSize=128m -Xss256k -XX:+DisableExplicitGC -XX:+UseConc ...

  4. JVM系统属性 OS环境变量 JVM启动参数

    JVM系统属性(System Properties) 1.不支持通过文件查看和设置系统属性 2.可以通过JDK自带的工具jvisulavm.exe查看 3.可以在Java程序中使用API来查看系统属性 ...

  5. CMS GC启动参数优化配置

    简介: java启动参数共分为三类: 其一是标准参数(-),所有的JVM实现都必须实现这些参数的功能,而且向后兼容: 其二是非标准参数(-X),默认jvm实现这些参数的功能,但是并不保证所有jvm实现 ...

  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. Java获取系统环境变量(System Environment Variable)和系统属性(System Properties)以及启动参数的方法

    系统环境变量(System Environment Variable): 在Linux下使用export $ENV=123指定的值.获取的方式如下: Map<String,String> ...

  8. 【java】java获取JVM启动参数 System.getProperty

    java获取JVM启动参数 System.getProperty取 -D后的key即可 public class Test { public static void main(String[] arg ...

  9. JVM组成、GC回收机制、算法、JVM常见启动参数、JAVA出现OOM,如何解决、tomcat优化方法

    JVM组成.GC回收机制.算法.JVM常见启动参数.JAVA出现OOM,如何解决.tomcat优化方法

随机推荐

  1. 如何创建R包并将其发布在 CRAN / GitHub 上--转载

    转载--https://www.analyticsvidhya.com/blog/2017/03/create-packages-r-cran-github/ 什么是 R 包?我开始创建 R 包的原因 ...

  2. _itemmod_refresh

    -- 随机FM刷新设置-- 小技巧:很多服所说的装备鉴定效果可以通过这个实现,也可以对物品重新生成新的附魔--详细解说一下鉴定系统如何实现--1首先在_itemmod_enchant_groups中添 ...

  3. win 10 安装visual studio 2010

    链接: https://pan.baidu.com/s/1JzA2Ei8NEGRPck253NUM9g 提取码: 52pt 点击下一步即可.

  4. mysql-5.6.41-winx64安装

    安装包 链接:https://pan.baidu.com/s/11-Ts3SrfJViQEtdtI_ik9w 提取码:cxt3 1.解压 将下载好的mysql-5.6.41-winx64.zip的安装 ...

  5. linq to sql and linq to object 总结

    Enumable类型是linq to object   是一个很特殊的类型   这个类型的数据源都是在程序的内存中 Queryable类型是 Linq to sql   对数据库进行操作都是这个类型  ...

  6. 力扣(LeetCode) 217. 存在重复元素

    给定一个整数数组,判断是否存在重复元素. 如果任何值在数组中出现至少两次,函数返回 true.如果数组中每个元素都不相同,则返回 false. 示例 1: 输入: [1,2,3,1] 输出: true ...

  7. powerdesiger 导入sqlserver 方法

    https://jingyan.baidu.com/album/7f766daf465e9c4101e1d0d5.html

  8. Oracle DB , 计算各个用户/schema 的磁盘占用空间

    http://www.dba-oracle.com/t_find_size_schema.htm Question:  How do I find the size of a schema in my ...

  9. 表结构中updated_time设计为ON UPDATE CURRENT_TIMESTAMP时,使用过程的一个坑

    一.mysql表结构中存在如下设计时 表结构中updated_time设计为ON UPDATE CURRENT_TIMESTAMP时,如下 `updated_time` datetime NOT NU ...

  10. 20171024xlVBA批量获取PPT\WORD\PDF页数

    Public Sub ModifyFileNames() Dim FolderPath As String Dim FileNames As Variant Dim dotPos As Long Di ...