NestJS集成googleapis服务致服务器Node.js内存溢出问题
问题背景
我有一个运行在约2GB内存的Ubuntu服务器(PM2托管)上的NestJS后端应用。添加GoogleIapService服务文件前应用运行正常,一旦添加该文件,Node就会崩溃并抛出错误:FATAL ERROR: Ineffective mark-compacts near heap limit Allocation failed - JavaScript heap out of memory。启动后内存持续增长至约950MB且不会回落。
环境信息
- Node.js: v18.x(或其他版本)
- NestJS: v10.x
- googleapis: 最新版
- 进程管理器:PM2(fork模式)
- 服务器:Ubuntu,约2GB内存
- 应用类型:REST + Socket.IO后端
服务代码
@Injectable() export class GoogleIapService implements OnModuleInit, OnModuleDestroy { private androidPublisher; private googleAuth; private authClient; private isInitialized = false; private google; private cleanupTimeout: NodeJS.Timeout | null = null; constructor() { console.log(`Google IAP Initialized!`); } async onModuleInit() { if (this.isInitialized) return; try { this.google = await import('googleapis').then((m) => m.google); const serviceAccountJson = process.env.GOOGLE_SERVICE_ACCOUNT_JSON; if (!serviceAccountJson) { throw new Error('GOOGLE_SERVICE_ACCOUNT_JSON is not set'); } const serviceAccountKey = JSON.parse(serviceAccountJson); this.googleAuth = new this.google.auth.GoogleAuth({ credentials: serviceAccountKey, scopes: ['https://www.googleapis.com/auth/androidpublisher'], }); this.authClient = await this.googleAuth.getClient(); this.androidPublisher = this.google.androidpublisher({ version: 'v3', auth: this.authClient, }); this.isInitialized = true; } catch (err) { this.isInitialized = false; await this.forceCleanup(); throw err; } } async onModuleDestroy() { await this.cleanup(); } private async cleanup() { this.androidPublisher = null; this.authClient = null; this.googleAuth = null; this.google = null; this.isInitialized = false; } private forceCleanup() { this.androidPublisher = null; this.authClient = null; this.googleAuth = null; this.google = null; this.isInitialized = false; } }
日志信息
<--- Last few GCs ---> Mark-Compact (reduce) ~953 MB FATAL ERROR: Ineffective mark-compacts near heap limit Allocation failed - JavaScript heap out of memory
已尝试的解决方法
- 将Node堆内存上限提升至2GB(
--max-old-space-size=2048) - 使用动态导入懒加载googleapis
- 确保服务仅初始化一次
- 实现
OnModuleDestroy进行资源清理 - 清除授权客户端、令牌、监听器及相关引用
- 初始化失败时强制清理资源
核心疑问
为何在NestJS服务中仅导入并初始化googleapis就会导致小型服务器上内存持续增长并最终溢出?即便已做清理并提升堆内存仍未解决,这是googleapis/GoogleAuth的预期行为,还是我在长运行NestJS进程中的初始化方式有误?
问题分析与解决方案
1. googleapis库的内存开销特性
googleapis是包含所有Google API客户端的全量库,即便你只用到androidpublisher模块,动态导入时仍会加载整个库的核心结构与依赖,本身就会占用数百MB内存。在2GB内存的服务器上,加上NestJS、Socket.IO本身的内存占用,很容易触达Node的堆内存上限。
另外,GoogleAuth内部会维护令牌缓存、自动刷新令牌的定时器以及全局状态,这些对象可能存在内存引用无法被GC回收的情况,在长运行进程中会导致内存缓慢泄漏。
2. 初始化方式的潜在问题
你的代码虽做了清理,但存在几个关键问题:
- 动态导入
googleapis后,库的全局实例会留在Node的模块缓存中,即便把this.google设为null,这部分内存也无法释放。 GoogleAuth和authClient内部可能存在未被正确清理的事件监听器或定时器(比如令牌刷新任务),会阻止对象被垃圾回收。
3. 针对性解决方法
(1)仅导入需要的API模块
不要导入整个googleapis库,直接导入单个API客户端,大幅减少内存占用:
// 替换原动态导入逻辑 const { androidpublisher_v3 } = await import('googleapis/build/src/apis/androidpublisher/v3'); const { GoogleAuth } = await import('google-auth-library');
调整初始化代码:
this.googleAuth = new GoogleAuth({ credentials: serviceAccountKey, scopes: ['https://www.googleapis.com/auth/androidpublisher'], }); this.authClient = await this.googleAuth.getClient(); this.androidPublisher = new androidpublisher_v3.Androidpublisher({ auth: this.authClient, });
这种方式只会加载androidpublisher相关代码,内存占用可减少50%以上。
(2)优化GoogleAuth的资源管理
- 显式停止
authClient的令牌刷新定时器(针对JWT客户端): - 在cleanup方法中移除事件监听器:
private async cleanup() { // 停止令牌刷新定时器 if (this.authClient?.stopRefreshTimer) { this.authClient.stopRefreshTimer(); } // 移除所有事件监听器 if (this.googleAuth?.removeAllListeners) { this.googleAuth.removeAllListeners(); } this.androidPublisher = null; this.authClient = null; this.googleAuth = null; this.google = null; this.isInitialized = false; }
(3)调整Node内存参数与PM2配置
服务器系统进程会占用部分内存,实际留给Node的内存不足2GB,建议调整内存参数并在PM2配置中明确指定:
{ "apps": [ { "name": "your-app", "script": "dist/main.js", "node_args": "--max-old-space-size=1536", "exec_mode": "fork" } ] }
(4)延迟初始化
如果服务不是启动时就需要Google IAP功能,改为第一次调用相关接口时再初始化,避免启动时占用大量内存:
async getAndroidPublisher() { if (!this.isInitialized) { await this.onModuleInit(); } return this.androidPublisher; }
总结
内存溢出的核心原因是googleapis全量库的内存开销过大,加上长运行进程中部分资源未被正确回收。通过仅导入所需API模块、优化资源清理逻辑、调整内存参数这几个步骤,基本可以解决2GB服务器上的内存问题。
内容的提问来源于stack exchange,提问作者Haider Ganaii

