LWJGL3的内存管理,第三篇,剩下的两种策略
LWJGL3的内存管理,第三篇,剩下的两种策略
上一篇讨论的基于 MemoryStack 类的栈上分配方式,是效率最高的,但是有些情况下无法使用。比如需要分配的内存较大,又或许生命周期较长。这时候就可以考虑使用 MemoryUtil 类来进行内存分配。
MemoryUtil
在内部实现中,MemoryUtil 是通过JNI调用本地库用作Allocator来完成功能。截至目前,LWJGL3支持的内存库有:
- rpmalloc (项目地址:https://github.com/mjansson/rpmalloc)
- jemalloc(项目地址:https://github.com/jemalloc/jemalloc)
- 标准C
MemoryUtil 对这些库进行了封装,提供了统一的API,使用 memAlloc 和 memFree 方法就可完成内存的分配与释放。
这种策略在redis的实现中也能看到,事实上 jemalloc 就是 redis 默认的内存分配器,当然它也支持别的分配器,比如 glibe、tcmalloc 等,在编译时通过参数指定即可。jemalloc 是一个通用的内存分配器实现,强调避免内存碎片和可伸缩的并发支持,它是从 FreeBSD 孵化出来的项目,官网有介绍:http://jemalloc.net/
在 LWJGL3 提供的一系列依赖包中,有一项依赖是
<dependency>
<groupId>org.lwjgl</groupId>
<artifactId>lwjgl-jemalloc</artifactId>
<classifier>${lwjgl.natives}</classifier>
</dependency>
其中 ${lwjgl.natives} 是平台标识符。以Windows平台为例,填写 natives-windows。得到的包为 lwjgl-jemalloc-3.2.3-natives-windows.jar,里面其实是一个jemalloc.dll。LWJGL3会在启动时解压该Jar包,并进行DLL加载。
LWJGL3 在启动时,将会根据启动参数定位配置的内存分配器(MEMORY_ALLOCATOR),如果用户没有配置,则默认加载 “org.lwjgl.system.jemalloc.JEmallocAllocator”类
Class.forName(className);
类加载将会触发 JEmallocAllocator 内部的静态块进行执行
getLibrary 最终会通过JNI调用到native代码
JNIEXPORT jlong JNICALL Java_org_lwjgl_system_windows_WinBase_nLoadLibrary(JNIEnv *__env, jclass clazz, jlong nameAddress) {
LPCTSTR name = (LPCTSTR)(intptr_t)nameAddress;
jlong __result;
UNUSED_PARAMS(__env, clazz)
__result = (jlong)(intptr_t)LoadLibrary(name);
saveLastError();
return __result;
}
第一次看到这个代码是,一直没有找到 LoadLibrary 方法的实现。后来发现其实这是一个 Windows API,https://docs.microsoft.com/en-us/windows/win32/api/libloaderapi/nf-libloaderapi-loadlibraryw。 该 API 会将dll加载到本地进程空间,并返回方法地址,以支持随后的调用。
在某些场景中, MemoryUtil 也有可能不适用
- 由于需要手动释放,可能在某些特定情况下会增加代码的复杂度
- 不确定要分配的内存什么时候可以回收
如果遇到这些问题,就可以考虑最后一种策略,就是 JDK 提供的 ByteBuffer.allocateDirect() 。
BufferUtils (ByteBuffer.allocateDirect)
这是JDK自带的直接内存分配方式,使用简单。LWJGL3 提供了相应的API进行封装
public static ByteBuffer createByteBuffer(int capacity) {
return ByteBuffer.allocateDirect(capacity).order(ByteOrder.nativeOrder());
}
封装的目的有两个
提供统一的API,将来内部实现变更时,依然可以向下兼容,API的使用者不需要太了解实现细节
内部实现可以进行优化,比如指定字节序
ByteBuffer::allocateDirect 作为JDK提供的标准方法,在LWJGL3中相当不推荐使用,还是因为性能的考虑,LWJGL3的应用通常对性能敏感,且可能负载较高。
ByteBuffer::allocateDirect 的缺点在于:
- 但是无法通过正常途径按需手动释放不需要的内存
- 而且通常需要两轮GC才能完成内存释放,在高负载情况下容易导致 OOM。
为什么 ByteBuffer::allocateDirect 的自动内存回收机制需要两轮 GC
这个说法来自于LWJGL3的内存管理FAQ,见 https://github.com/LWJGL/lwjgl3-wiki/wiki/1.3.-Memory-FAQ,下面我们分析一下。
DirectByteBuffer 类的构造函数中,会构建一个 Cleaner的实例
cleaner = Cleaner.create(this, new Deallocator(base, size, cap));
其中 Deallocator 是一个 Runnable 实例,run方法里是用于释放内存的代码
public void run() {
if (address == 0) {
// Paranoia
return;
}
unsafe.freeMemory(address);
address = 0;
Bits.unreserveMemory(size, capacity);
}
所以关键在于,run 方法何时得到执行。但遗憾的是本身该方法是由JVM来调度的,要像确定整个过程比较麻烦。暂时没有找到更好的方法来证实这一点,不过我发现在Phantom reference类的注释里其实也有类似的说法:
/**
* Phantom reference objects, which are enqueued after the collector
* determines that their referents may otherwise be reclaimed. Phantom
* references are most often used for scheduling pre-mortem cleanup actions in
* a more flexible way than is possible with the Java finalization mechanism.
*
* <p> If the garbage collector determines at a certain point in time that the
* referent of a phantom reference is <a
* href="package-summary.html#reachability">phantom reachable</a>, then at that
* time or at some later time it will enqueue the reference.
*
* <p> In order to ensure that a reclaimable object remains so, the referent of
* a phantom reference may not be retrieved: The <code>get</code> method of a
* phantom reference always returns <code>null</code>.
*
* <p> Unlike soft and weak references, phantom references are not
* automatically cleared by the garbage collector as they are enqueued. An
* object that is reachable via phantom references will remain so until all
* such references are cleared or themselves become unreachable.
*
* @author Mark Reinhold
* @since 1.2
*/
public class PhantomReference<T> extends Reference<T> {...
结合我们的例子,翻译一下就是说,对于一个 Cleaner 对象,GC在决定它的 referents(DirectByteBuffer)可以回收时,会把这个Cleaner入队。常用于执行 scheduling pre-mortem cleanup ,比 Java 的 finalization 机制(不明白这个机制具体指什么)更灵活。
如果GC在某个时间点认为一个 Cleaner 是 phantom reachable ,即只有虚引用能引用到 Cleaner,则GC将会将它 enqueue(通过执行Reference::enqueue)。
为了确保保留可回收对象,不能通过get方法获取到虚引用的referent,即虚引用的get方法将始终返回null。
与软引用或弱引用不同,虚引用不是垃圾收集器将其enqueue时自动清除。 虚引用可达的对象将保持不变,直到所有此类引用被清理或它们自身变得不可达。
总的来说,对于直接内存的回收,仍然是一个不确定的行为。
附:带中文注释的 Cleaner 类代码,中文注释来源于 https://zhuanlan.zhihu.com/p/29454205
public class Cleaner extends PhantomReference<Object>
{
// Dummy reference queue, needed because the PhantomReference constructor
// insists that we pass a queue. Nothing will ever be placed on this queue
// since the reference handler invokes cleaners explicitly.
// dummyQueue 并没有实际用处,仅仅是因为 PhantomReference 的构造函数强制要求传入一个Queue
private static final ReferenceQueue<Object> dummyQueue = new ReferenceQueue<>();
// Doubly-linked list of live cleaners, which prevents the cleaners
// themselves from being GC'd before their referents
// 所有的cleaner都会被加到一个双向链表中去,这样做是为了保证在referent被回收之前
// 这些Cleaner都是存活的。
static private Cleaner first = null;
private Cleaner
next = null,
prev = null;
// 构造的时候把自己加到双向链表中去
private static synchronized Cleaner add(Cleaner cl) {
if (first != null) {
cl.next = first;
first.prev = cl;
}
first = cl;
return cl;
}
// clean方法会调用remove把当前的cleaner从链表中删除。
private static synchronized boolean remove(Cleaner cl) {
// If already removed, do nothing
if (cl.next == cl)
return false;
// Update list
if (first == cl) {
if (cl.next != null)
first = cl.next;
else
first = cl.prev;
}
if (cl.next != null)
cl.next.prev = cl.prev;
if (cl.prev != null)
cl.prev.next = cl.next;
// Indicate removal by pointing the cleaner to itself
cl.next = cl;
cl.prev = cl;
return true;
}
// 用户自定义的一个Runnable对象,
private final Runnable thunk;
// 私有构造函数,保证了用户无法单独地使用new来创建Cleaner。
private Cleaner(Object referent, Runnable thunk) {
super(referent, dummyQueue);
this.thunk = thunk;
}
/**
* 所有的Cleaner都必须通过create方法进行创建。
*/
public static Cleaner create(Object ob, Runnable thunk) {
if (thunk == null)
return null;
return add(new Cleaner(ob, thunk));
}
/**
* 这个方法会被Reference Handler线程调用,来清理资源。
*/
public void clean() {
if (!remove(this))
return;
try {
thunk.run();
} catch (final Throwable x) {
AccessController.doPrivileged(new PrivilegedAction<Void>() {
public Void run() {
if (System.err != null)
new Error("Cleaner terminated abnormally", x)
.printStackTrace();
System.exit(1);
return null;
}});
}
}
}
LWJGL3的内存管理,第三篇,剩下的两种策略的更多相关文章
- LWJGL3的内存管理,第一篇,基础知识
LWJGL3的内存管理,第一篇,基础知识 为了讨论LWJGL在内存分配方面的设计,我将会分为数篇随笔分开介绍,本篇将主要介绍一些大方向的问题和一些必备的知识. 何为"绑定(binding)& ...
- LWJGL3的内存管理,第二篇,栈上分配
LWJGL3的内存管理,第二篇,栈上分配 简介 为了讨论LWJGL在内存分配方面的设计,本文将作为该系列随笔中的第二篇,用来讨论在栈上进行内存分配的策略,该策略在 LWJGL3 中体现为以 Memor ...
- LWJGL3的内存管理
LWJGL3的内存管理 LWJGL3 (Lightweight Java Game Library 3),是一个支持OpenGL,OpenAl,Opengl ES,Vulkan等的Java绑定库.&l ...
- LWJGL3的内存管理,简介及目录
LWJGL3的内存管理,简介及目录 LWJGL3 (Lightweight Java Game Library 3),是一个支持OpenGL,OpenAl,Opengl ES,Vulkan等的Java ...
- 垃圾回收GC:.Net自己主动内存管理 上(三)终结器
垃圾回收GC:.Net自己主动内存管理 上(三)终结器 垃圾回收GC:.Net自己主动内存管理 上(一)内存分配 垃圾回收GC:.Net自己主动内存管理 上(二)内存算法 垃圾回收GC:.Net自己主 ...
- JAVA高级篇(二、JVM内存模型、内存管理之第二篇)
本文转自https://zhuanlan.zhihu.com/p/25713880. JVM的基础概念 JVM的中文名称叫Java虚拟机,它是由软件技术模拟出计算机运行的一个虚拟的计算机. JVM也充 ...
- spark内存管理这一篇就够了
1. 堆内和堆外内存规划 1.1 堆内内存 堆内内存的大小,由 Spark 应用程序启动时的 –executor-memory 或 spark.executor.memory 参数配置.Executo ...
- 死磕Spring之AOP篇 - Spring AOP两种代理对象的拦截处理
该系列文章是本人在学习 Spring 的过程中总结下来的,里面涉及到相关源码,可能对读者不太友好,请结合我的源码注释 Spring 源码分析 GitHub 地址 进行阅读. Spring 版本:5.1 ...
- Linux内存管理解析(三) : 内核对内核空间的内存管理
内核采用 struct page 来表示一个物理页,在其中记载了诸多物理页的属性,比如 物理页被几个线程使用(如若没有则表示该页可以释放),页对应的虚拟地址. 首先需要知道的是,分配物理页可以分为两个 ...
随机推荐
- Raft 协议
Paxos 存在的问题 Paxos 算法的描述偏学术化,缺失了很多细节,无法直接应用于工程领域.实际工程应用中的分布式算法大多是 Paxos 的变种,验证这些算法的正确性也成为了一个难题. 举个例子: ...
- Unicode、UTF8、GB2312、ANSI
来源:https://blog.csdn.net/osanwenyu/article/details/48439461 版权声明:本文为博主原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原 ...
- dockerfile-maven-plugin极简教程
目录 一.简介 二.概述 三.将spring-boot-app打包成docker镜像 创建示例应用 修改pom文件 增加Dockerfile文件 使用Maven打包应用 运行应用镜像 四.分析mvn ...
- 用 shell 脚本做 restful api 接口监控
问题的提出 基于历史原因,公司有一个"三无"采集服务--无人员.无运维.无监控--有能力做的部门不想接.接了的部门没能力.于是就一直这样裸奔,直到前几天一个依赖于这个采集服务的大数 ...
- markdown的基本使用
1.什么是markdown? markdown是一种轻量级的标记语言 可以转换为html/xhtml和其它格式 可读.直观.学习成本低 当你学会使用markdown编写文档时,你会感觉自己发现了一个新 ...
- 发布MeteoInfo 2.3
主要的更新如下: Using SVG icons in GUI. Update netCDF java library to 5.3.3. Update FlatLaf to 0.40. Update ...
- MeteoInfoLab脚本示例:中尺度气旋散点图
全球长时间序列中尺度气旋数据(http://cioss.coas.oregonstate.edu/eddies/)有netCDF格式,散点数据类型,只有一个很大的维Nobs = 2590938.尝试读 ...
- day24 Pyhton学习 反射
一.isinstance,type,issubclass issubclass() 这个内置函数可以帮我们判断x类是否是y类的子类 issubclass(x,y) class Base: pass c ...
- centos8安装php7.4
一,下载php7.4 1,官方网站: https://www.php.net/ 2,下载 [root@yjweb source]# wget https://www.php.net/distribut ...
- centos8平台给sudo配置日志
一,sudo日志的用途: 我们可以记录下来用户账号在哪个时间进行过sudo 这样不需要再从secure日志中查找用户的sudo记录 说明:刘宏缔的架构森林是一个专注架构的博客,地址:https://w ...