java中使用堆外内存,关于内存回收需要注意的事和没有解决的遗留问题(等大神解答)

JVM可以使用的内存分外2种:堆内存和堆外内存,堆内存完全由JVM负责分配和释放,如果程序没有缺陷代码导致内存泄露,那么就不会遇到java.lang.OutOfMemoryError这个错误。使用堆外内存,就是为了能直接分配和释放内存,提高效率。JDK5.0之后,代码中能直接操作本地内存的方式有2种:使用未公开的Unsafe和NIO包下ByteBuffer。

关于Unsafe对象的简介和获取方式,可以参考我的另一篇博客  java获取Unsafe类的实例和取消eclipse编译的错误 

使用ByteBuffer分配本地内存则非常简单,直接ByteBuffer.allocateDirect(10 * 1024 * 1024)即可。

C语言的内存分配和释放函数malloc/free,必须要一一对应,否则就会出现内存泄露或者是野指针的非法访问。java中我们需要手动释放获取的堆外内存吗?

1、首先我们看下NIO中提供的ByteBuffer

import java.nio.ByteBuffer;

public class TestDirectByteBuffer
{
	// -XX:MaxDirectMemorySize=40M
	public static void main(String[] args) throws Exception
	{
		while (true)
		{	<pre name="code" class="java"><span style="white-space:pre">		</span>      ByteBuffer buffer = ByteBuffer.allocateDirect(10 * 1024 * 1024);

}}}


我们将最大堆外内存设置成40M,运行这段代码会发现:程序可以一直运行下去,不会报OutOfMemoryError。如果使用了-verbose:gc -XX:+PrintGCDetails,会发现程序频繁的进行垃圾回收活动。于是我们可以得出结论:ByteBuffer.allocateDirect分配的堆外内存不需要我们手动释放,而且ByteBuffer中也没有提供手动释放的API。也即是说,使用ByteBuffer不用担心堆外内存的释放问题,除非堆内存中的ByteBuffer对象由于错误编码而出现内存泄露。

2、接下来我们看下直接在方法体中使用Unsafe的效果

import sun.misc.Unsafe;

public class TestUnsafeMemo
{
	// -XX:MaxDirectMemorySize=40M
	public static void main(String[] args) throws Exception
	{
		Unsafe unsafe = GetUsafeInstance.getUnsafeInstance();

		while (true)
		{
			long pointer = unsafe.allocateMemory(1024 * 1024 * 20);
			System.out.println(unsafe.getByte(pointer + 1));

			// 如果不释放内存,运行一段时间会报错java.lang.OutOfMemoryError
			// unsafe.freeMemory(pointer);
		}
	}

}

这段程序会报OutOfMemoryError错误,也就是说allocateMemory和freeMemory,相当于C语音中的malloc和free,必须手动释放分配的内存

3、类似于ByteBuffer,将Unsafe分配内存封装到一个类中

import sun.misc.Unsafe;

public class ObjectInHeap
{
	private long address = 0;

	private Unsafe unsafe = GetUsafeInstance.getUnsafeInstance();

	public ObjectInHeap()
	{
		address = unsafe.allocateMemory(2 * 1024 * 1024);
	}

	// Exception in thread "main" java.lang.OutOfMemoryError
	public static void main(String[] args)
	{
		while (true)
		{
			ObjectInHeap heap = new ObjectInHeap();
			System.out.println("memory address=" + heap.address);
		}
	}
}

这段代码会抛出OutOfMemoryError。这是因为ObjectInHeap对象是在堆内存中分配的,当该对象被垃圾回收的时候,并不会释放堆外内存,因为使用Unsafe获取的堆外内存,必须由程序显示的释放,JVM不会帮助我们做这件事情。由此可见,使用Unsafe是有风险的,很容易导致内存泄露。

4、正确释放Unsafe分配的堆外内存

虽然第3种情况的ObjectInHeap存在内存泄露,但是这个类的设计是合理的,它很好的封装了直接内存,这个类的调用者感受不到直接内存的存在。那怎么解决ObjectInHeap中的内存泄露问题呢?可以覆写Object.finalize(),当堆中的对象即将被垃圾回收器释放的时候,会调用该对象的finalize。由于JVM只会帮助我们管理内存资源,不会帮助我们管理数据库连接,文件句柄等资源,所以我们需要在finalize自己释放资源。

import sun.misc.Unsafe;

public class RevisedObjectInHeap
{
	private long address = 0;

	private Unsafe unsafe = GetUsafeInstance.getUnsafeInstance();

	// 让对象占用堆内存,触发[Full GC
	private byte[] bytes = null;

	public RevisedObjectInHeap()
	{
		address = unsafe.allocateMemory(2 * 1024 * 1024);
		bytes = new byte[1024 * 1024];
	}

	@Override
	protected void finalize() throws Throwable
	{
		super.finalize();
		System.out.println("finalize." + bytes.length);
		unsafe.freeMemory(address);
	}

	public static void main(String[] args)
	{
		while (true)
		{
			RevisedObjectInHeap heap = new RevisedObjectInHeap();
			System.out.println("memory address=" + heap.address);
		}
	}

}

我们覆盖了finalize方法,手动释放分配的堆外内存。如果堆中的对象被回收,那么相应的也会释放占用的堆外内存。这里有一点需要注意下

// 让对象占用堆内存,触发[Full GC
private byte[] bytes = null;

这行代码主要目的是为了触发堆内存的垃圾回收行为,顺带执行对象的finalize释放堆外内存。如果没有这行代码或者是分配的字节数组比较小,程序运行一段时间后还是会报OutOfMemoryError。这是因为每当创建1个RevisedObjectInHeap对象的时候,占用的堆内存很小(就几十个字节左右),但是却需要占用2M的堆外内存。这样堆内存还很充足(这种情况下不会执行堆内存的垃圾回收),但是堆外内存已经不足,所以就不会报OutOfMemoryError。

虽然改进后的RevisedObjectInHeap不会有堆外内存泄露,但是这种解决方法却无端地浪费了堆内存。简单的看了下ByteBuffer的源码,它内部分配堆外内存也是通过unsafe.allocateMemory()实现的。那ByteBuffer又是怎么实现的堆外内存释放呢?难道也是通过第4种类似RevisedObjectInHeap的做法吗?欢迎大神指点迷津啊!

时间: 2024-10-28 14:19:40

java中使用堆外内存,关于内存回收需要注意的事和没有解决的遗留问题(等大神解答)的相关文章

java中的堆和栈

Java把内存分成两种,一种叫做栈内存,一种叫做堆内存 在函数中定义的一些基本类型的变量和对象的引用变量都是在函数的栈内存中分配.当在一段代码块中定义一个变量时,java就在栈中为这个变量分配内存空间,当超过变量的作用域后,java会自动释放掉为该变量分配的内存空间,该内存空间可以立刻被另作他用. 堆内存用于存放由new创建的对象和数组以及包装类.在堆中分配的内存,由java虚拟机自动垃圾回收器来管理.在堆中产生了一个数组或者对象后,还可以在栈中定义一个特殊的变量,这个变量的取值等于数组或者对象

java中的堆、栈、常量池

java中的堆.栈.常量池 分类: java2010-01-15 03:03 4248人阅读 评论(5) 收藏 举报 javastring编译器jvm存储equals Java内存分配: 1. 寄存器:我们在程序中无法控制2. 栈:存放基本类型的数据和对象的引用,但对象本身不存放在栈中,而是存放在堆中3. 堆:存放用new产生的数据4. 静态域:存放在对象中用static定义的静态成员5. 常量池:存放常量6. 非RAM(随机存取存储器)存储:硬盘等永久存储空间-----------------

java中的堆内存和栈内存

Java把内存分成两种: 一种叫做栈内存 一种叫做堆内存 栈内存 : 在函数中定义的一些基本类型的变量和对象的引用变量都是在函数的栈内存中分配.当在一段代码块中定义一个变量时,java就在栈中为这个变量分配内存空间,当超过变量的作用域后,java会自动释放掉为该变量分配的内存空间,该内存空间可以立刻被另作他用. 堆内存 : 堆内存用于存放由new创建的对象和数组.在堆中分配的内存,由java虚拟机自动垃圾回收器来管理.在数组和对象在没有引用变量指向它的时候,才变成垃圾,不能再被使用,但是仍然占着

Java中的substring真的会引起内存泄露么

在Java中开发,String是我们开发程序可以说必须要使用的类型,String有一个substring方法用来截取字符串,我们想必也常常使用.但是你知道么,关于Java 6中的substring是否会引起内存泄露,在国外的论坛和社区有着一些讨论,以至于Java官方已经将其标记成bug,并且为此Java 7 还重新进行了实现.读到这里可能你的问题就来了,substring怎么会引起内存泄露呢?那么我们就带着问题,走进小黑屋,看看substring有没有内存泄露,又是怎么导致所谓的内存泄露. 基本

Java中由substring方法引发的内存泄漏

在Java中我们无须关心内存的释放,JVM提供了内存管理机制,有垃圾回收器帮助回收不需要的对象.但实际中一些不当的使用仍然会导致一系列的内存问题,常见的就是内存泄漏和内存溢出 内存溢出(out of memory ):通俗的说就是内存不够用了,比如在一个无限循环中不断创建一个大的对象,很快就会引发内存溢出. 内存泄漏(leak of memory):是指为一个对象分配内存之后,在对象已经不在使用时未及时的释放,导致一直占据内存单元,使实际可用内存减少,就好像内存泄漏了一样. 由substring

Java中this、static关键字的内存图解

Java中的关键字有很多,abstract  default  goto*  null  switch  boolean  do  if  package  nchronzed  break  double  implements  private  this  byte  else  import  protected  throw  throws  case  extends  instanceof  public  transient  catch  false  int  return 

java中的堆和栈的概念和区别

在说堆和栈之前,我们先说一下JVM(虚拟机)内存的划分: Java程序在运行时都要开辟空间,任何软件在运行时都要在内存中开辟空间,Java虚拟机运行时也是要开辟空间的.JVM运行时在内存中开辟一片内存区域,启动时在自己的内存区域中进行更细致的划分,因为虚拟机中每一片内存处理的方式都不同,所以要单独进行管理. JVM内存的划分有五片: 1.   寄存器: 2.   本地方法区: 3.   方法区: 4.   栈内存: 5.   堆内存. 我们重点来说一下堆和栈: 栈内存:栈内存首先是一片内存区域,

Java中的堆与栈

一.栈 栈的优势是,存取速度比堆要快,仅次于直接位于CPU中的寄存器,当超过变量的作用域后,java会自动释放掉为该变量分配的内存空间,该内存空间可以立刻被另作他用.但缺点是,存在栈中的数据大小与生存期必须是确定的,缺乏灵活性.栈数据可以共享. 那些数据存放在栈中? 基本数据类型(int, short, long, byte, float, double, boolean, char)的变量存放于栈中. 对象的引用存放于栈中. 栈数据共享具体指什么意思? int a = 3; int b = 3

Java中的堆和栈的区别

当一个人开始学习Java或者其他编程语言的时候,会接触到堆和栈,由于一开始没有明确清晰的说明解释,很多人会产生很多疑问,什么是堆,什么是栈,堆和栈有什么区别?更糟糕的是,Java中存在栈这样一个后进先出(Last In First Out)的顺序的数据结构,这就是java.util.Stack.这种情况下,不免让很多人更加费解前面的问题.事实上,堆和栈都是内存中的一部分,有着不同的作用,而且一个程序需要在这片区域上分配内存.众所周知,所有的Java程序都运行在JVM虚拟机内部,我们这里介绍的自然