System.gc()和-XX:+DisableExplicitGC启动参数,以及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的内存释放的更多相关文章
- JVM相关 - 深入理解 System.gc()
本文基于 Java 17-ea,但是相关设计在 Java 11 之后是大致一样的 我们经常在面试中询问 System.gc() 究竟会不会立刻触发 Full GC,网上也有很多人给出了答案,但是这些答 ...
- JVM启动参数详解
JVM启动参数以及具体的解释: -Xmx1024M 最大堆内存 -Xms1024M 初始化堆内存,正常和最大堆内存相同,减少动态改变的内存损耗 -Xmn384M 年轻代内存 -XX:PermSize= ...
- 【JVM】jvm启动参数
-server -Xmx2048m -Xms1500m -Xmn1024m -XX:PermSize=128m -Xss256k -XX:+DisableExplicitGC -XX:+UseConc ...
- JVM系统属性 OS环境变量 JVM启动参数
JVM系统属性(System Properties) 1.不支持通过文件查看和设置系统属性 2.可以通过JDK自带的工具jvisulavm.exe查看 3.可以在Java程序中使用API来查看系统属性 ...
- CMS GC启动参数优化配置
简介: java启动参数共分为三类: 其一是标准参数(-),所有的JVM实现都必须实现这些参数的功能,而且向后兼容: 其二是非标准参数(-X),默认jvm实现这些参数的功能,但是并不保证所有jvm实现 ...
- 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 ...
- Java获取系统环境变量(System Environment Variable)和系统属性(System Properties)以及启动参数的方法
系统环境变量(System Environment Variable): 在Linux下使用export $ENV=123指定的值.获取的方式如下: Map<String,String> ...
- 【java】java获取JVM启动参数 System.getProperty
java获取JVM启动参数 System.getProperty取 -D后的key即可 public class Test { public static void main(String[] arg ...
- JVM组成、GC回收机制、算法、JVM常见启动参数、JAVA出现OOM,如何解决、tomcat优化方法
JVM组成.GC回收机制.算法.JVM常见启动参数.JAVA出现OOM,如何解决.tomcat优化方法
随机推荐
- mysql索引使用
原文:http://www.jianshu.com/p/2b541c028157 索引是快速搜索的关键.MySQL索引的建立对于MySQL的高效运行是很重要的.下面介绍几种常见的MySQL索引类型.在 ...
- 踩坑记录:ubuntu下,http代理无法修改的问题
事情经过: 今天在ubuntu下使用http代理的时候,碰到一个奇怪的现象.就是在当前shell窗口下,输入“env | grep proxy”,显示的http_proxy一直都存在,即使我修改了本 ...
- gzip: stdin: not in gzip format tar: Child returned status 1 tar: Error is not recoverable: exiting now
[root@Gris- FMIS2600bak]# tar -zxvf /home/oradata/FMIS2600DMP.tar.gz gzip: stdin: not in gzip format ...
- VC异常.简单抛,简单捕获
1.ZC:始终没有找到,能像Delphi7里面那样能抛 字符串描述 的异常信息.(难道Delphi是这样做的?:在程序的最外围 套了一层异常捕获,然后在获取到异常之后,自己再将异常信息弹出来,我们写的 ...
- [osg]osgcallback各种回调使用的例子介绍
观察MyReadFileCallback结构体的内容,可以发现它继承自osgDB::Registry::ReadFileCallback,并重载了一个函数readNode,分析源代码可知,该函数在os ...
- 远程Service的显示 / 隐式启动
在进程间通信时,常会设计开启远程 Service 的情况.开启远程 Service 的方式有两种,一种时显示开启,一种是隐式开启.下面分别来看: 一.隐式开启 服务端:Service 所在 Andro ...
- JAVA基础知识总结:一到二十二全部总结
>一: 一.软件开发的常识 1.什么是软件? 一系列按照特定顺序组织起来的计算机数据或者指令 常见的软件: 系统软件:Windows\Mac OS \Linux 应用软件:QQ,一系列的播放器( ...
- 单调队列 Monotonic Queue / 单调栈 Monotonic Stack
2018-11-16 22:45:48 一.单调队列 Monotone Queue 239. Sliding Window Maximum 问题描述: 问题求解: 本题是一个经典的可以使用双端队列或者 ...
- Birdman Quotes
Popularity is the slutty little cousin of prestige (威望,声望) . A man becomes a critic when he cannot b ...
- android -------- Data Binding的使用(三)Observable
解决:databinding 中 ViewModel数据发生改变,View中也要改变(实时更新) BaseObservable 在ViewModel 中可以继承 BaseObservable publ ...