当前位置:首页 > 物联网 > 智能应用
[导读]256MB跑Java,OOM通常不是“堆太小”单一问题,而是堆+元空间+代码缓存+线程栈+DirectBuffer+JVM自身开销一起超物理内存。调优原则是“全量封顶、对象复用、缓存有界、堆外可控、OOM可兜底”。


256MB跑Java,OOM通常不是“堆太小”单一问题,而是堆+元空间+代码缓存+线程栈+DirectBuffer+JVM自身开销一起超物理内存。调优原则是“全量封顶、对象复用、缓存有界、堆外可控、OOM可兜底”。

一、JVM启动全量封顶

小内存边缘机别用默认策略,显式限定每一块非堆区域:

java -Xms96m -Xmx96m \

 -XX:+UseSerialGC \

 -XX:MaxMetaspaceSize=32m -XX:MetaspaceSize=16m \

 -XX:ReservedCodeCacheSize=24m -XX:InitialCodeCacheSize=8m \

 -XX:MaxDirectMemorySize=24m \

 -Xss256k \

 -XX:+ExitOnOutOfMemoryError \

 -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/var/log/heap.hprof \

 -Xlog:gc*:file=/var/log/gc.log:time,uptime:filecount=5,filesize=8m \

 -jar edge-app.jar

96MB堆+32MB元空间+24MB代码缓存+24MB直接内存+线程栈,其余留给OS、JIT、栈和系统缓冲;多核且延迟敏感可换G1并把堆提到128MB,但256MB总内存下Serial更省Remembered Set开销。 线程多就把-Xss降到192k~256k,100个线程按256k约25MB,按1MB默认会吃掉100MB。

二、对象池消灭短生命周期垃圾

传感器报文、JSON临时对象、字节数组全部池化,避免Eden频繁GC:

public class ByteArrayPool {

   private final int size;

   private final java.util.concurrent.ConcurrentLinkedQueue<byte[]> q

           = new java.util.concurrent.ConcurrentLinkedQueue<>();

   public ByteArrayPool(int size) { this.size = size; }

   public byte[] acquire() {

       byte[] b = q.poll();

       return b != null ? b : new byte[size];

   }

   public void release(byte[] b) {

       if (b != null && b.length == size) q.offer(b);

   }

}

温湿度、Modbus报文固定长度,用定长byte[]/ByteBuffer,不在循环里new String、new ArrayList。字符串拼接改StringBuilder并复用,大文本解析按流分块。

三、缓存有界,杜绝无限Map

设备影子、点表、配置用容量上限+软引用,内存紧张时GC可回收:

import java.lang.ref.SoftReference;

import java.util.LinkedHashMap;

import java.util.Map;


public class BoundedCache<K,V> {

   private final int max;

   private final LinkedHashMap<K,SoftReference<V>> map;

   public BoundedCache(int max){

       this.max=max;

       this.map=new LinkedHashMap<>(16,0.75f,true){

           protected boolean removeEldestEntry(Map.Entry<K,SoftReference<V>> e){

               if(size()<=max) return false;

               SoftReference<V> r=e.getValue();

               return r==null||r.get()==null;

           }

       };

   }

   public synchronized V get(K k){

       SoftReference<V> r=map.get(k);

       return r==null?null:r.get();

   }

   public synchronized void put(K k,V v){ map.put(k,new SoftReference<>(v)); }

}

硬缓存必须设max,软引用只作二级兜底;计量、历史曲线落SQLite/文件,不长期留堆内。

四、堆外DirectBuffer池化限流

NIO收MQTT/Modbus-TCP、串口透传用直接内存,但必须限总量、用完归还:

import java.nio.ByteBuffer;

import java.util.Queue;

import java.util.concurrent.ConcurrentLinkedQueue;


public class DirectPool {

   private final Queue<ByteBuffer> pool = new ConcurrentLinkedQueue<>();

   private final int cap, limit;

   private int used = 0;

   public DirectPool(int cap,int limit){this.cap=cap;this.limit=limit;}

   public synchronized ByteBuffer acquire(){

       ByteBuffer b=pool.poll();

       if(b!=null) return b;

       if(used>=limit) return ByteBuffer.allocate(cap); // 兜底用堆内,防堆外耗尽

       used++;

       return ByteBuffer.allocateDirect(cap);

   }

   public void release(ByteBuffer b){

       if(b.isDirect()){ b.clear(); pool.offer(b); }

   }

}

MaxDirectMemorySize必须和池上限匹配;Netty场景用PooledByteBufAllocator,不用裸allocateDirect堆积。 监控直接内存:

for(var p:ManagementFactory.getPlatformMXBeans(java.lang.management.BufferPoolMXBean.class)){

   if("direct".equals(p.getName()))

       System.out.println("direct used="+p.getMemoryUsed()+"/"+p.getMaxSize());

}

五、线程与类加载收口

动态代理、Groovy、热加载会撑爆Metaspace,边缘固件尽量关动态代理;每类加载器不用即卸载。线程用固定池,不用Executors.newCachedThreadPool:

ExecutorService io = new ThreadPoolExecutor(4,8,30L,TimeUnit.SECONDS,

       new LinkedBlockingQueue<>(256), r->{

           Thread t=new Thread(r,"iot-io"); t.setStackSize(256 * 1024); return t;

       });

协程/虚拟线程在低版本不适用,256MB老JDK用少量实线程更稳。

六、运行期监控与OOM兜底

定时采堆、元空间、直接内存、线程数,超阈值主动降级:

ScheduledExecutorService sc = Executors.newSingleThreadScheduledExecutor();

sc.scheduleAtFixedRate(()->{

   var mem=ManagementFactory.getMemoryMXBean();

   long heapUsed=mem.getHeapMemoryUsage().getUsed();

   if(heapUsed > 0.85*mem.getHeapMemoryUsage().getMax()){

       cacheClearHook(); System.gc();   // 仅应急,不常规调用

   }

},1,5,TimeUnit.MINUTES);

配合ExitOnOutOfMemoryError让systemd快速重启,HeapDumpOnOutOfMemoryError留现场,事后用jstat -gc、jmap -histo、MAT看是缓存、线程还是DirectBuffer泄漏。

256MB设备按“堆96~128MB、Serial/G1按核数选、元空间32MB、直接内存24MB、Xss256k、对象池+有界软缓存+Direct池、GC日志+BufferPool监控+OOMdump”闭环,可把OOM降到极低;若还出现Metaspace或Unable to create native thread,优先砍动态类加载和线程数,而不是继续加堆。



本站声明: 本文章由作者或相关机构授权发布,目的在于传递更多信息,并不代表本站赞同其观点,本站亦不保证或承诺内容真实性等。需要转载请联系该专栏作者,如若文章内容侵犯您的权益,请及时联系本站删除( 邮箱:macysun@21ic.com )。
关闭