随着项目规模不断扩大,系统中一些细微之处便可能引发内存溢出(OOM)或频繁垃圾回收(GC),进而导致系统停顿时间增加、吞吐量下降。以下将介绍几种在实际开发中有助于节省内存的常见良好实践。
一、合理使用 `intern()` 方法
`String.intern()` 方法的逻辑是:如果字符串常量池中已存在当前字符串,则返回池中该字符串的引用;否则,将该字符串对象加入常量池,并返回其引用。
自 Java 7 起,字符串常量池被移至堆内存中,因此也可受 GC 管理并适时回收。字符串常量池通过 `StringTable` 进行维护,其中存储的是强引用。那么 GC 如何回收这些引用呢?
在 Java 8 中,Full GC 触发时会扫描 `StringTable`,并移除那些不再可达的字符串引用。
我们可以利用这一机制,将一些常用但又担心引起 OOM 的字符串交由常量池管理,从而减少频繁创建字符串对象所带来的开销。
然而,此方法需谨慎使用。如果不触发 Full GC,这些常量将始终驻留于内存中,持续占用空间,同时也会增加 GC 的负担。
二、优先使用基本类型而非包装类
在方法内部声明 `Integer x = 100;` 时,编译器会自动执行装箱操作,即转换为 `Integer.valueOf(100)`。基本类型直接存储在栈中,且一个 `int` 仅占用 4 字节。
装箱后,除了在 128 到 127 范围内(该范围内的数值已缓存,不会创建新对象)之外,超出此范围的数值均会创建新对象,从而增加 GC 压力。
此外,内存占用也会显著增加:从原来的 4 字节可能扩大到 16~24 字节。这部分额外开销主要来源于对象头(通常为 8~16 字节,具体取决于 JVM 实现及是否开启指针压缩),以及可能的对齐填充(为满足内存对齐要求而补充的字节)。
三、利用逃逸分析优化内存分配
如果编译器能够判定某个在方法内部创建的对象不会“逃逸”出该方法,则可将该对象分配在栈上而非堆中。这样做的好处是对象会随着方法执行结束而自动回收,无需垃圾回收器介入。
在 Java 8 中,并未实现传统意义上的“栈上分配”,而是采用了标量替换的概念:将一个对象拆分为其组成的基本类型字段,在实际运行过程中并不会创建完整的对象实例。
那么,什么是对象的“逃逸”与“不逃逸”呢?
不逃逸:对象仅在当前方法或线程内使用,外部无法访问。
逃逸:对象被赋值给类的成员变量、作为方法返回值、传递给其他线程等情况。
需要注意的是,即便对象作为参数传递给其他方法,编译器也不一定认定其逃逸。编译器会尝试进行方法内联优化,即将调用方法的内容直接展开到当前方法中,随后再进行逃逸分析。
如果对象仅用于值的读取或修改(例如 `a.x = 1`),则通常不会被视为逃逸;而若将对象存入全局变量(例如 `list.add(a)`),由于编译器无法确定该对象是否会被其他方法访问,则会被判定为逃逸,从而直接在堆上分配内存。
通过养成上述编码习惯,可以在一定程度上优化内存使用,降低 GC 频率与停顿时间,从而提升大型应用的运行效率与稳定性。在实际开发中,应结合具体场景合理运用这些技巧,并辅以适当的内存监控与分析工具进行验证。
来源:小程序app开发|ui设计|软件外包|IT技术服务公司-木风未来科技-成都木风未来科技有限公司