虚引用对外内存管理

本文探讨了堆外内存的原理,包括其与堆内内存的区别,优势如减少垃圾回收和提高速度,以及如何通过DirectByteBuffer进行设置、创建和手动/自动回收。特别关注了内存控制、清理机制以及避免内存溢出的方法。
摘要由CSDN通过智能技术生成

1.什么是堆外内存?
堆外内存和堆内内存是两个相对的概念,其中堆内内存(on-heap memory)是我们平常工作中接触比较多的。Java中分配的非空对象都是由Java虚拟机的垃圾收集器管理的,都是放在堆内内存。我们可以通过jvm参数-Xms,-Xmx等设置堆的大小和最大值。

jvm会采用垃圾回收器(GC)统一进行内存管理,GC会在某些特定的时间点进行一次彻底回收,也就是Full GC,Full GC会对所有分配的堆内内存进行扫描,在这个过程中会对JAVA应用程序的性能造成一定影响,还可能会产生Stop The World。

和堆内内存相对应,堆外内存就是把内存对象分配在Java虚拟机的堆以外的内存,这些内存直接直接受操作系统管理(而不是java虚拟机)。

2.堆外内存有什么优势?
减少垃圾回收:因为垃圾回收会对其他的应用产生影响
加快了复制的速度:堆内在flush到远程时,会先复制到直接内存(非堆内存),然后再发送;而存储在堆外内存相当于省略掉了这个工作。
堆外内存的缺点就是内存难以控制,使用了堆外内存就间接失去了JVM管理内存的可行性,需要自己管理,当发生内存溢出时排查起来非常困难。

3.堆外内存的使用
堆外内存不是 JVM 运行时数据区 Runtime Data Area 的一部分,这部分内存区域直接被操作系统管理。

通常我们使用 java.nio.DirectByteBuffer 对象进行堆外内存的管理和使用,它会在对象创建的时候就分配堆外内存。

3.1堆外内存的设置
堆外内存的限额默认与堆内内存(由-Xmx 设定)相仿,可用 -XX:MaxDirectMemorySize 进行设定。

当使用达到了阈值的时候将调用 System.gc 来做一次 Full GC,以此来回收掉没有被使用的堆外内存。

3.2堆外内存的创建
在使用DirectByteBuffer 时,首先会向 Bits 类申请存储额度。Bits 类内部维护着当前已经使用的堆外内存值,Bits 类有一个全局的 totalCapacity 变量,记录着全部 DirectByteBuffer 的总大小,每次申请,都先看看是否超过最大值:

如果超过最大值,会主动执行 Sytem.gc(),期待能主动回收一点堆外内存。然后休眠100ms,看看 totalCapacity 降下来没有,如果内存还是不足,就会抛出内存溢出。
如果额度被批准,就调用 sun.misc.Unsafe 去分配内存,返回内存基地址,Unsafe 的是C++实现,标准的 malloc。然后再调一次 Unsafe 把这段内存给清零。
使用DirectByteBuffer的注意事项

java.nio.DirectByteBuffer 对象在创建过程中会先通过Unsafe接口通过os::malloc来分配内存,然后将内存的起始地址和大小存到java.nio.DirectByteBuffer 对象里,这样就可以直接操作这些内存。这些内存只有在DirectByteBuffer 回收掉之后才有机会被回收,因此如果这些对象大部分都移到了old,但是一直没有触发CMS GC或者 Full GC,那么悲剧将会发生,因为你的物理内存被他们耗尽了,因此为了避免这种悲剧的发生,一定要通过-XX:MaxDirectMemorySize 来指定最大的堆外内存大小,当使用达到了阈值的时候将调用System.gc来做一次 full gc,以此来回收掉没有被使用的堆外内存。

3.3堆外内存的回收
3.3.1自动回收
Java 是不用用户去管理内存的,所以 Java 对堆外内存的管理也是自动回收的。它是由 GC 模块负责的,在 GC 时会扫描 DirectByteBuffer 对象是否有有效引用指向该对象,如没有,在回收 DirectByteBuffer 对象的同时且会回收其占用的堆外内存。但是 JVM 如何释放其占用的堆外内存呢?

这得从 Cleaner 继承了 PhantomReference(虚引用) 说起。说到 Reference,还有 SoftReference、WeakReference、FinalReference 他们作用各不相同,这里就不展开说了。

简单介绍 PhantomReference,首先虚引用是不会影响 JVM 去回收其指向的对象,当 GC 某个对象时,如果有此对象上还有虚引用对其引用,会将 PhantomReference 对象插入 ReferenceQueue 队列。

PhantomReference插入到哪个队列呢?看 PhantomReference 类代码,其继承自 Reference,Reference 对象有个 ReferenceQueue 成员,这个也就是 PhantomReference 对象插入的 ReferenceQueue 队列,此成员如果不由外部传入就是 ReferenceQueue.NULL。如果需要通过 queue 拿到 PhantomReference 对象,这个 ReferenceQueue 对象还是必须由外部传入。

这里可以看到一种尴尬的情况,因为 DirectByteBuffer 本身的个头很小,只要熬过了 Young GC,即使已经失效了也能在老生代1里舒服的呆着,不容易把老生代撑爆触发 Full GC,如果没有别的大块头进入老生代触发Full GC,就一直在那耗着,占着一大片堆外内存不释放。

3.3.2手动回收
手动回收,就是由开发手动调用 DirectByteBuffer 的 cleaner 的 clean 方法来释放空间。由于 cleaner 是 private 访问权限,所以自然想到使用反射来实现。

还有另一种方法,DirectByteBuffer 实现了 DirectBuffer 接口,这个接口有 cleaner 方法可以获取 cleaner 对象。

对于 Sun 的 JDK 这其实很简单,只要从 DirectByteBuffer 里取出那个 sun.misc.Cleaner,然后调用它的 clean() 就行。

1.何为内存模块 (Memory Module)? 内存模块是指一个印刷电路板表面上有镶嵌数个记忆体芯片chips,而这内存芯片通常是DRAM芯片,但近来系统设计也有使用快取隐藏式芯片镶嵌在内存模块上内存模块是安装在PC 的主机板上的专用插槽(Slot)上镶嵌在Module上DRAM芯片(chips)的数量和个别芯片(chips)的容量,是决定内存模块的设计的主要因素。 2.什么是Parity? 早先所使用的存储器数据检错方式是Parity Check,其是以每8比特增加1比特的方式进行检错。因此若是具备Parity的存储器,其数据宽度将比非parity的存储器为大。不过若以 Parity的检错方式看,其多出的奇偶位事实上只用于分辨奇数或偶数个比特数。这种方式仅能得知是某一位发生错误,并无法确定是哪一个位置发生错误,而且无法运用在双数位的检错上。 3.什么是ECC? ECC是另一种更为进步的存储器数据检错机制。其工作的方式与Parity不同,并不是采用单一比特的方式来进行检错,而是采用数据块(block)概念与复杂的演算方法来修正数据。因此不仅能检测多位比特错误,还能进行修正单一比特的错误。 4.SPD的作用是什么? 为Serial Presence Detect 的缩写,它是烧录在EEPROM内的码,以往开机时BIOS必须侦测memory,但有了SPD就不必再去作侦测的动作,而由BIOS直接读取 SPD取得内存的相关资料。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值