OpenGL中复用位置、法线、纹理缓冲区避免内存泄漏的方法
OpenGL树木绘制内存泄漏问题
在OpenGL中绘制树木对时间和内存资源消耗较大。目前我实现了随角色移动定期重建含几十棵树的网格,但每次重新分配树木顶点缓冲区时,可用内存持续下降。尝试在每次重新分配后触发垃圾回收,仅能减缓内存泄漏速度,请问有没有方法彻底避免该问题?
内存追踪代码
Log.d("memorytest","maketree before TreeA usedMemInMB "+((gamepanel.runtime.totalMemory() - gamepanel.runtime.freeMemory()) / 1048576L)+ " maxHeapSizeInMB "+(gamepanel.runtime.maxMemory() / 1048576L)+" availHeapSizeInMB "+ ((gamepanel.runtime.maxMemory() / 1048576L)-((gamepanel.runtime.totalMemory() - gamepanel.runtime.freeMemory())/1048576L))); myglrenderer.TreeAGridPositions = ByteBuffer.allocateDirect(terrain_positiondata.length * mBytesPerFloat) .order(ByteOrder.nativeOrder()).asFloatBuffer(); myglrenderer.TreeAGridPositions.put(terrain_positiondata).position(0); myglrenderer.TreeAGridNormals = ByteBuffer.allocateDirect(terrain_normaldata.length * mBytesPerFloat) .order(ByteOrder.nativeOrder()).asFloatBuffer(); myglrenderer.TreeAGridNormals.put(terrain_normaldata).position(0); myglrendererTreeAGridTextures = ByteBuffer.allocateDirect(terrain_texturedata.length * mBytesPerFloat) .order(ByteOrder.nativeOrder()).asFloatBuffer(); myglrenderer.TreeAGridTextures.put(terrain_texturedata).position(0); Log.d("memorytest","maketree before TreeB usedMemInMB "+((gamepanel.runtime.totalMemory() - gamepanel.runtime.freeMemory()) / 1048576L)+ " maxHeapSizeInMB "+(gamepanel.runtime.maxMemory() / 1048576L)+" availHeapSizeInMB "+ ((gamepanel.runtime.maxMemory() / 1048576L)-((gamepanel.runtime.totalMemory() - gamepanel.runtime.freeMemory())/1048576L))); myglrenderer.TreeBGridPositions = ByteBuffer.allocateDirect(terrain_positiondata.length * mBytesPerFloat) .order(ByteOrder.nativeOrder()).asFloatBuffer(); myglrenderer.TreeBGridPositions.put(terrain_positiondata).position(0); myglrenderer.TreeBGridNormals = ByteBuffer.allocateDirect(terrain_normaldata.length * mBytesPerFloat) .order(ByteOrder.nativeOrder()).asFloatBuffer(); myglrenderer.TreeBGridNormals.put(terrain_normaldata).position(0); myglrendererTreeBGridTextures = ByteBuffer.allocateDirect(terrain_texturedata.length * mBytesPerFloat) .order(ByteOrder.nativeOrder()).asFloatBuffer(); myglrenderer.TreeBGridTextures.put(terrain_texturedata).position(0); Log.d("memorytest","maketree after TreeB usedMemInMB "+((gamepanel.runtime.totalMemory() - gamepanel.runtime.freeMemory()) / 1048576L)+ " maxHeapSizeInMB "+(gamepanel.runtime.maxMemory() / 1048576L)+" availHeapSizeInMB "+ ((gamepanel.runtime.maxMemory() / 1048576L)-((gamepanel.runtime.totalMemory() - gamepanel.runtime.freeMemory())/1048576L)));
未触发垃圾回收的测试结果
---- maketree before TreeA usedMemInMB 286 maxHeapSizeInMB 512 availHeapSizeInMB 226 maketree after TreeA usedMemInMB 335 maxHeapSizeInMB 512 availHeapSizeInMB 177 maketree after TreeB usedMemInMB 396 maxHeapSizeInMB 512 availHeapSizeInMB 116
maketree before TreeA usedMemInMB 384 maxHeapSizeInMB 512 availHeapSizeInMB 128 maketree after TreeA usedMemInMB 437 maxHeapSizeInMB 512 availHeapSizeInMB 75 maketree after TreeB usedMemInMB 433 maxHeapSizeInMB 512 availHeapSizeInMB 79 ----- maketree before TreeA usedMemInMB 429 maxHeapSizeInMB 512 availHeapSizeInMB 83 maketree after TreeA usedMemInMB 500 maxHeapSizeInMB 512 availHeapSizeInMB 12 maketree after TreeB usedMemInMB 501 maxHeapSizeInMB 512 availHeapSizeInMB 11 ----- outofmemoryerror
触发垃圾回收后的测试结果
maketree before TreeA usedMemInMB 292 maxHeapSizeInMB 512 availHeapSizeInMB 220 maketree after TreeA usedMemInMB 346 maxHeapSizeInMB 512 availHeapSizeInMB 166 maketree after TreeB usedMemInMB 397 maxHeapSizeInMB 512 availHeapSizeInMB 115 maketree after Runtime.getRuntime().gc() usedMemInMB 376 maxHeapSizeInMB 512 availHeapSizeInMB 136 ---- maketree before TreeA usedMemInMB 392 maxHeapSizeInMB 512 availHeapSizeInMB 120 maketree after TreeA usedMemInMB 404 maxHeapSizeInMB 512 availHeapSizeInMB 108 maketree after TreeB usedMemInMB 448 maxHeapSizeInMB 512 availHeapSizeInMB 64 maketree after Runtime.getRuntime().gc() usedMemInMB 383 maxHeapSizeInMB 512 availHeapSizeInMB 129 ---- maketree before TreeA usedMemInMB 433 maxHeapSizeInMB 512 availHeapSizeInMB 79 maketree after TreeA usedMemInMB 432 maxHeapSizeInMB 512 availHeapSizeInMB 80 maketree after TreeB usedMemInMB 501 maxHeapSizeInMB 512 availHeapSizeInMB 11 maketree after Runtime.getRuntime().gc() usedMemInMB 457 maxHeapSizeInMB 512 availHeapSizeInMB 55 ---- maketree before TreeA usedMemInMB 492 maxHeapSizeInMB 512 availHeapSizeInMB 20 maketree after TreeA usedMemInMB 485 maxHeapSizeInMB 512 availHeapSizeInMB 27 maketree after TreeB usedMemInMB 510 maxHeapSizeInMB 512 availHeapSizeInMB 2 maketree after Runtime.getRuntime().gc() usedMemInMB 467 maxHeapSizeInMB 512 availHeapSizeInMB 45 ---- maketree before TreeA usedMemInMB 485 maxHeapSizeInMB 512 availHeapSizeInMB 27 outofmemoryerror
解决方案
1. 复用Direct ByteBuffer,避免重复分配
你当前每次重建都用ByteBuffer.allocateDirect()创建新的直接缓冲区,而直接缓冲区的内存不在JVM堆中,GC无法自动回收(或回收时机不可控)。正确做法是预先分配足够大的缓冲区,后续只更新内容不重新创建:
- 初始化阶段一次性分配好
TreeAGridPositions、TreeAGridNormals等缓冲区的最大容量 - 重建网格时,调用
clear()重置缓冲区后重新put数据,无需再次allocateDirect
示例代码:
// 初始化(仅执行一次) int maxPositionByteSize = 预估的最大位置数据字节数; myglrenderer.TreeAGridPositions = ByteBuffer.allocateDirect(maxPositionByteSize) .order(ByteOrder.nativeOrder()).asFloatBuffer(); // 重建网格时(重复执行) myglrenderer.TreeAGridPositions.clear(); myglrenderer.TreeAGridPositions.put(terrain_positiondata).position(0);
2. 显式释放旧缓冲区引用
如果必须重新分配缓冲区,先将旧缓冲区引用置为null,让JVM识别可回收对象:
// 分配新缓冲区前释放旧引用 myglrenderer.TreeAGridPositions = null; myglrenderer.TreeAGridNormals = null; myglrenderer.TreeAGridTextures = null; // 再分配新缓冲区 myglrenderer.TreeAGridPositions = ByteBuffer.allocateDirect(...);
注:直接缓冲区的回收依赖底层系统,置null后可能需等待一段时间,但比持有旧引用效果好。
3. 用GPU端VBO管理顶点数据
避免在CPU端反复创建缓冲区,改用GPU的顶点缓冲对象(VBO):
- 初始化时创建VBO并绑定数据
- 重建网格时,调用
glBufferSubData()更新GPU端缓冲区内容,无需重复创建VBO
4. 优化树木网格数据
- 实例化绘制:如果多棵树是相同模型,只存一份顶点数据,用
glDrawArraysInstanced或glDrawElementsInstanced批量绘制,大幅减少内存占用 - 简化模型:降低树木多边形数量,减少单棵树的顶点数据量
内容的提问来源于stack exchange,提问作者Androidcoder
相关产品推荐
相关产品推荐

