关于OOM 2

本文针对3 - tier架构应用系统常见的Out of Memory和Memory leak问题,从JVM基本架构入手,介绍了相关定义、JVM内存管理重要名称,分析了问题发生原因及JVM处置方式,还给出了处理Java Out of Memory问题的步骤,以帮助有效掌握资源。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一般在3-tier 架構下的應用系統,最常見的問題就是Out of Memory(記憶體

不足),或Memory leak(記憶體洩漏)的狀況,最後往往導致Application Server

失效與系統Crash,讓管理人員必需常常守候在Server 旁邊,注意它關心它系

統狀況與運作情形避免它Crash

而本專題報導則是針對此類型問題的發生,從JVM 其基本架構開始說明,

並採用問答與實例的方式進行說明解釋,並且提供檢查的項目說明,可藉由這些

項目自我檢測,以避免發生Memory leak 有效掌握Resource

何謂Out of MemoryMemory leak

Out of Memory 定義

 

記憶體不論Java heap Native Memory 是不足以提供給元件使用。

Memory leak 定義

 

元件的記憶體使用不論在Java heap Native Memory 中持續成長,

最後導致發生Out of memory 的情況。

 

何謂Java heap, Native memory and Process Size

要了解Memory leak 問題的發生,首先先了解幾個JVM 的記憶體管理重要

的名稱:

Java heap

這是JVM 用來配置Java objects 的記憶體,Java heap 記憶體大小是透過

命令執行列中下的參數-Xmx 設定的。假如最大的heap size 沒有定義,那麼它

的大小限制將由JVM 視當時情況如機器的實體記憶體與剩下可用的記憶體決

定。因此一般都建議要設定最大的Java heap 的值。

Native Memory

這是JVM 用來它內部運作的記憶體,Native Memory Heap 將是會被JVM

使用,而它的大小取決於產生的程式碼、產生的threadGC 時用於保存java

object 資訊與產生或最佳化程式碼時的暫存空間

假如它是Third party native module,它將可能使用Native Memory.

如:native JDBC driver 就是配置Native Memory

Native Memory 的最大值是會受限於任何一OS virtual process size 與已

經被參數-Xmx 指定給Java Heap 的記憶體大小。例如:假如應用系統能夠總共

配置3GB,並且假如最大的Java Heap 大小為1G,那麼Native Memory 最大

值可能接近2GB

Process Size

Process Size 將會是Java HeapNative Memory 與被用於已載入執行與函

式庫的記憶體的加總,在32 位元的作業系統,一個處理程序虛擬定址空間能夠

4GB;若超過4GB,作業系統的核心將會預留一部份給它自己使用(一般是

1~2GB)。那麼剩下的就是給應用系統。

Windows:假設有4GB 的記憶體,預設給應用系統使用最大2GB 與而另外

2GB 是給核心使用。僅管如此,在一些不同版本的Windows,可以用/3GB

參數切換這個比例值,讓應用系統可以得到3GB 的記憶體。詳細可參考Microsoft

網站,址址:

http://msdn.microsoft.com/library/default.asp?url=/library/en-us/ddtools/hh/ddt

ools/bootini_1fcj.asp

Red Hat AS 2.1:應用系統可以使用到最大記憶體大小為3GB

其他的作業系統,請參考該作業系統文件做設定。

Process 定址空間與實體記憶體的不同

每一個Process 都擁有自己的定址空間,在32 位元作業系統,這個定址空

間是介於0 4GB 之間。這是機器上獨立的RAM Swap Space,機器上全部

的實體記憶體是同一台機器上的RAM Swap Space 的加總,所有執行中的

Process 分享這個實體記憶體。

Process 的記憶體定址是虛擬的。作業系統核心對應這虛擬位址到實體位

址。實體位址指到實體記憶體中的某一個位置。在一台機器上任何特定時間所有

被正在執行的Process 的虛擬記憶體,其加總不能超出在同一台機器上全部實

體記憶體大小。

 

為何會Out of Memory 問題發生?在這個狀況發生

JVM 會做什麼處置?

Out of Memory in java heap

假如JVM 無法在java heap 取得記憶體來配置更多的java objectsJVM

丟出java out of memory 錯誤,JVM 無法配置更多的java objects 假如heap

塞滿了正在使用的objects java heap 無法再擴展。

在這個狀況下,在丟出java.lang.OutOfMemoryError 錯誤訊息後,JVM

讓應用系統決定要做什麼。例如:應用系統自行監控這個錯誤並且決定在那一個

的模式下停止執行,或者不管這個錯誤。假如應用系統不處理這個錯誤,那麼

thread 會丟出這個錯誤訊息並且停止執行離開JVM(假如使用java thread

dump,你將不會看到這個thread)

WebLogic Server 在這個狀況,假如它是由一個execute thread 丟出,這個

錯誤將會被監控並且會做記錄。假如這是連續不斷被丟出來,那麼core health

monitor thread 會停止WebLogic Server 運作。

Out of Memory in native heap

假如無法native memory 配置到記憶體空間,則JVM 丟出native out of

memory,這通常發生在Process 達到作業系統Process size 的限制或是機器執

行超出RAM Swap Space 加總.當這個發生時,JVM 會處理native out memory 情況,記錄訊息說明它執行到 out of native memory 或無法取得記憶體並且離開。假如JVM 或任何被載入的module(像是libc 或是一個Third party module)無法處理這個native out ofmemory 狀況,然後作業系統將會傳送一個sigabort 訊息給JVM,這樣將會使JVM 停止離開。通當JVM 將會產生程式碼檔當它取得sigabort 訊號。

 

處理此問題步驟

Java Out of Memory

1. 收集與分析verbose gc 的錯誤訊息輸出

06-15
<think>我们正在讨论单机多线程环境下的OOM问题。OOM(OutOfMemory)错误是指程序在申请内存时,没有足够的内存空间供其使用,进而抛出错误。在多线程环境下,由于多个线程同时运行,可能会并发地申请内存资源,更容易导致内存耗尽。下面我将从原因和解决方案两个方面进行详细说明。###一、多线程环境下OOM的主要原因1.**线程过多导致栈内存溢出(StackOverflowError或OutOfMemoryError)**-每个线程都会为其栈分配内存(默认1MB,可通过`-Xss`调整)[^2]。当线程数过多时,总栈内存可能超过JVM限制。-**计算公式**:$\text{总栈内存}=\text{线程数}\times\text{每个线程栈大小}$-例如:默认栈大小1MB,创建1000个线程需要约1GB栈内存,若JVM总内存不足则发生OOM2.**线程池配置不当**-**无界任务队列**:如使用`LinkedBlockingQueue`且未设置容量,任务激增时队列无限增长导致堆内存溢出[^3]。-**线程数失控**:`newCachedThreadPool()`理论上可无限创建线程(见案例):```javaExecutorServicepool=Executors.newCachedThreadPool();while(true){pool.execute(()->{try{Thread.sleep(1000000);}catch(InterruptedExceptione){}});}```最终因线程数过多触发OOM。3.**共享资源竞争引发内存泄漏**-**场景**:多线程频繁操作静态集合(如`Map`/`List`)且未正确清理。-**案例**:Web应用中将用户会话数据存入静态Map但未移除:```javapublicclassSessionManager{privatestaticMap<Long,Session>sessions=newConcurrentHashMap<>();//添加后未实现超时移除逻辑publicstaticvoidaddSession(Longid,Sessionsession){sessions.put(id,session);}}```随着时间推移,`sessions`持续增长直至OOM。4.**死锁或阻塞导致资源无法释放**-线程死锁使得部分对象无法被垃圾回收(即使逻辑上应回收)。-线程长时间阻塞(如I/O等待)导致关联对象滞留内存。###二、解决方案与实践优化####1.控制线程数量与生命周期-**限制最大线程数**:根据系统资源设置合理阈值(公式:$\text{maxThreads}=\frac{\text{总内存-堆预留}}{\text{线程栈大小}}$)。-**使用线程池替代裸线程**:通过`ThreadPoolExecutor`自定义参数:```javanewThreadPoolExecutor(corePoolSize,//核心线程数maxPoolSize,//最大线程数keepAliveTime,//空闲线程存活时间TimeUnit.MILLISECONDS,newLinkedBlockingQueue<>(1000)//有界队列);```####2.优化任务队列策略|队列类型|适用场景|防OOM措施||--------------------|-------------------------|------------------------||有界队列|可控任务量的系统|设置合理容量并配合拒绝策略||同步移交队列|高吞吐低延迟场景|需严格限制最大线程数||优先级队列|任务需分级处理|避免无限制增长|####3.内存泄漏防护-**代码规范**:-对长生命周期集合(如静态Map)使用`WeakReference`或定时清理。-避免在循环中创建大对象。-**工具检测**:-使用`jmap`生成堆转储文件,通过MAT分析对象引用链。-Arthas在线监控内存使用:`watchcom.example.Service*'{params,returnObj}'`####4.JVM参数调优-**调整栈大小**:根据线程数需求设置`-Xss256k`(减小默认栈大小)[^2]。-**合理分配堆内存**:年轻代大小推荐为堆的3/8(`-Xmn`参数)[^2]。-**示例配置**:```-Xmx4g-Xms4g//堆固定4G避免动态扩容开销-Xmn1.5g//年轻代1.5G(占3/8)-Xss256k//线程栈降为256KB```###三、典型案例分析>**案例1:日志服务异步队列OOM**>**现象**:日志服务使用无界队列接收日志,突发流量时队列堆积至2GB触发OOM。>**解决方案**:>1.将`LinkedBlockingQueue`改为有界队列(容量10000)>2.添加拒绝策略:丢弃超出容量的日志并告警>3.引入`GuavaRateLimiter`限流>**案例2:线程数失控**>**现象**:`newCachedThreadPool()`在并发连接激增时创建8000+线程,栈内存耗尽。>**优化**:改用有界线程池,参数计算:>$$\text{maxThreads}=\frac{\text{可用内存1GB}}{\text{线程栈512KB}}=2000\text{线程}$$>实际设置`maxPoolSize=500`(预留内存给堆使用)。###总结:多线程OOM防护体系```mermaidgraphTDA[预防]-->B[线程池参数优化]A-->C[有界队列+拒绝策略]A-->D[代码内存泄漏检查]E[诊断]-->F[jmap+MAT分析]E-->G[Arthas在线监控]H[治理]-->I[JVM调优]H-->J[限流降级]```>**关键原则**:>-线程是稀缺资源,需池化管理和约束上限>-内存分配需考虑线程开销(栈空间)>-多线程共享数据结构应具备自我清理能力§§相关问题§§1.如何通过JVM参数优化多线程应用的内存使用?2.哪些工具可以检测多线程环境下的内存泄漏?3.线程池的拒绝策略如何选择才能避免OOM?4.为什么说`newCachedThreadPool()`在突发流量下可能导致OOM?5.如何设计一个防OOM的异步任务处理系统?</think>### OOM问题在单机多线程环境下的原因及解决方案 #### 一、OOM的核心原因分析 ```mermaid graph TD A[多线程OOM原因] --> B[线程栈溢出] A --> C[堆内存泄漏] A --> D[元空间溢出] A --> E[直接内存溢出] ``` 1. **线程栈溢出(StackOverflowError/OOM)** - **原因**: - 线程数过多超出栈内存容量(默认1MB/线程)[^2] $$ \text{最大线程数} \approx \frac{\text{最大内存 - 堆内存}}{\text{线程栈大小}} $$ - 递归调用过深(非多线程特有但会被多线程放大) - **特征**:`java.lang.StackOverflowError`或`OutOfMemoryError: unable to create new native thread` 2. **堆内存泄漏(Heap OOM)** - **多线程特有场景**: - 静态集合(如`ConcurrentHashMap`)持续增长未清理 ```java // 危险代码示例 public static Map<Long, Object> cache = new ConcurrentHashMap<>(); void addData(Long id) { cache.put(id, new byte[1024*1024]); // 每个对象1MB } ``` - 线程池使用无界队列(如`LinkedBlockingQueue`)导致任务堆积[^3] - 死锁导致对象无法释放(两个线程互相持有对方需要的锁) 3. **元空间溢出(Metaspace OOM)** - **多线程影响**:高并发下类加载激增(如动态代理类生成) - **错误信息**:`OutOfMemoryError: Metaspace` 4. **直接内存溢出(Direct Buffer OOM)** - **场景**:多线程频繁操作`ByteBuffer.allocateDirect()` - **特征**:`OutOfMemoryError: Direct buffer memory` #### 二、解决方案与实践优化 ##### 1. 线程栈溢出应对 | 措施 | 实现方式 | 效果 | |-----------------------|-------------------------------------------------------------------------|--------------------------| | 限制最大线程数 | `ThreadPoolExecutor(maxPoolSize=50)` | 防止线程数失控[^3] | | 减小线程栈大小 | JVM参数:`-Xss256k`(默认1MB) | 同等内存支持更多线程[^2] | | 异步非阻塞模型 | 使用Netty/Vert.x替代线程池 | 1线程处理万级连接 | | 监控线程创建 | `jstack`或Arthas的`thread`命令 | 实时发现线程泄漏 | ##### 2. 堆内存泄漏治理 **代码层面:** ```java // 安全方案1:使用弱引用 Map<Long, WeakReference<Object>> cache = new ConcurrentHashMap<>(); // 安全方案2:定时清理 ScheduledExecutorService cleaner = Executors.newScheduledThreadPool(1); cleaner.scheduleAtFixedRate(() -> cache.clear(), 1, 1, TimeUnit.HOURS); ``` **配置优化:** - 线程池必须使用有界队列: ```java new ThreadPoolExecutor(..., new ArrayBlockingQueue<>(1000)); ``` - 设置合理的GC策略:`-XX:+UseG1GC -XX:MaxGCPauseMillis=200` **工具检测:** 1. `jmap -dump:format=b,file=heap.bin <pid>` 生成堆转储 2. MAT工具分析支配树(Dominator Tree),定位泄漏对象 ##### 3. 元空间与直接内存优化 | 内存区域 | 优化措施 | 参数示例 | |----------|--------------------------------------------------------------------------|------------------------------| | 元空间 | 增大元空间大小<br>关闭不需要的类加载 | `-XX:MaxMetaspaceSize=256m`<br>`-XX:+DisableAttachMechanism` | | 直接内存 | 限制直接内存用量<br>使用内存池(如Netty的PooledByteBufAllocator) | `-XX:MaxDirectMemorySize=128m` | #### 三、防御性编程实践 1. **资源约束策略** - 线程池必须设置: ```java new ThreadPoolExecutor( 10, // 核心线程数 100, // 最大线程数 60s, // 空闲超时 TimeUnit.SECONDS, new ArrayBlockingQueue<>(1000), // 有界队列 new ThreadPoolExecutor.AbortPolicy() // 拒绝策略 ); ``` 2. **内存监控接入** ```java // 示例:监控堆内存使用率 MemoryMXBean memoryBean = ManagementFactory.getMemoryMXBean(); if(memoryBean.getHeapMemoryUsage().getUsed() > maxHeap*0.8) { alert("内存使用超过80%!"); } ``` 3. **压力测试模型** ```mermaid graph LR A[模拟并发请求] --> B{监控指标} B --> C[JVM内存] B --> D[GC次数] B --> E[线程状态] C --> F[调整-Xmx] D --> G[优化GC参数] E --> H[调整线程池] ``` #### 四、典型案例分析 > **案例:电商系统大促OOM** > **现象**: > - 高峰期每秒5000请求 > - 线程池使用`newCachedThreadPool()`(无线程数限制) > - 2分钟内创建2000+线程触发`OOM: unable to create native thread` > > **解决方案**: > 1. 改用有界线程池:`maxPoolSize=500 + ArrayBlockingQueue(5000)` > 2. 添加拒绝策略:记录日志并返回友好错误 > 3. 增加JVM参数:`-Xss256k -XX:MaxMetaspaceSize=256m` > > **效果**: > | 指标 | 优化前 | 优化后 | > |-------------|-------------|-------------| > | 最大线程数 | 无限制 | 500 | > | 内存占用 | 8GB | 4GB | > | 错误率 | 23% | 0.5% | #### 总结:OOM防御体系 1. **资源隔离**:线程池约束 + 有界队列 2. **容量规划**:通过`(总内存 - 堆内存)/Xss`计算最大线程数[^2] 3. **监控预警**:JVM内存 + 线程状态实时监控 4. **优雅降级**:拒绝策略保核心业务(如`CallerRunsPolicy`) > **关键原则**: > - 多线程环境下,**所有资源必须设置明确上限** > - 线程不是越多越好,需满足:$ \text{线程数} \leq \frac{\text{可用内存}}{\text{线程栈}} $ > - 无界数据结构是OOM的主要诱因
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值