你有没有遇到过这种情况:写了个Java小工具,刚启动挺快,跑个十几分钟就开始变慢,甚至弹出“OutOfMemoryError”提示?别急着重装JDK,问题大概率出在Java的内存管理和垃圾回收(GC)机制上。
Java内存不是用完就扔,得有人收拾
和C/C++手动malloc/free不同,Java把内存分配和释放“包圆儿”了——对象在堆(Heap)里出生,等没人引用它了,就靠垃圾回收器来清理。听起来省心,但真用起来,堆大小设太小,GC频繁停顿;设太大,单次回收又拖慢响应。就像租房子:房间太小,东西堆不下天天要整理;房间太大,打扫一次得半天,还容易找不到东西。
常见GC行为,其实能看懂
运行Java程序时加个参数:
-XX:+PrintGCDetails -Xloggc:gc.log启动后,控制台或gc.log里会打出类似这样的记录:[GC (Allocation Failure) [PSYoungGen: 123456K->12345K(131072K)] 234567K->123456K(419430K), 0.0456789 secs]意思是:年轻代(PSYoungGen)原来用了123MB,GC后只剩12MB,整个堆从234MB降到123MB,耗时45毫秒。如果这行反复刷屏、耗时越来越长,说明内存压力大,或者对象“活得太久”,老是升到老年代。几个实用调整点,不用改代码
在启动脚本里试试这些参数:
① 控制堆大小
别让JVM自己猜,显式指定:
-Xms512m -Xmx2g初始堆512MB,最大2GB。避免运行中频繁扩容,减少GC次数。② 换个更“勤快”的回收器(Java 8/11常用)
比如用G1替代默认的Parallel GC:
-XX:+UseG1GC -XX:MaxGCPauseMillis=200G1适合堆大于4GB的场景,目标是200毫秒内完成一次GC,响应更稳。③ 避免对象“早熟”进老年代
如果发现大量短命对象很快被移到老年代,可以调小晋升阈值:
-XX:MaxTenuringThreshold=6默认是15,改成6意味着对象最多在年轻代熬过6次GC才升级,给回收多几次机会。顺手查一查,心里有底
程序跑着时,用JDK自带的jstat看看实时情况:
jstat -gc <pid> 2000每2秒刷新一次各代使用率、GC次数和耗时。如果Old区使用率持续上涨不回落,基本就是内存泄漏苗头了——比如缓存没清、监听器没注销、线程池里的ThreadLocal没remove。系统设置栏目不是只管Windows服务或注册表。对Java应用来说,启动参数就是它的“系统设置”。调对了,程序呼吸顺畅;调错了,再好的逻辑也卡在GC里喘不过气。