静态方法创建多实例的运行时内存占用及相关技术疑问
我读过不少关于静态类变量和静态方法加载的文章,但很少看到关于静态方法返回实例化对象及其内存占用的内容。
从静态方法返回实例是创建单例(singleton)的常见方式,但单例应该只创建一次,通过getInstance方法访问。
如果刻意打破单例模式,编写如下代码会发生什么?(注意RandomSumBuilder的add()方法是静态访问修饰符)
public class Service { public void doSomething() { for (int i = 0; i < 1000; i++) { System.out.println("Random sum : " + RandomSumBuilder.add().build()); } } } public class RandomSumBuilder { private List<Integer> aList = new ArrayList<>(); public RandomSumBuilder() { } public static RandomSumBuilder add() { RandomSumBuilder randomSumBuilder = new RandomSumBuilder(); randomSumBuilder.aList.add(new Random().nextInt(11)); return randomSumBuilder; } public int build() { return aList.stream() .reduce(Integer::sum) .orElse(0); } }
很明显RandomSumBuilder不是单例,但我有以下疑惑:
- 这些不同的
RandomSumBuilder实例是static的吗?(我认为是,就像单例一样) - 这些对象能被垃圾回收(garbage collection)吗?(基于我对问题1的回答,我认为不能)
- 这会给类加载器(class loader)带来不必要的负担吗?(如果要加载这么多
RandomSumBuilder来实例化这些静态对象,我认为是的,因为我看不出保留它们全局存在的意义。我用VisualVM做的快速本地测试无法帮我确定) - 使用内部类实现建造者模式(builder pattern)会带来什么不同?(我认为会大幅减少创建的静态对象数量和类加载负担,但连续四个假设让我不确定)
问题解答
这些实例不是static的
static是修饰类成员(变量、方法)的关键字,和对象实例本身无关。这里的RandomSumBuilder实例是在静态方法add()内通过new创建的普通对象,既没有被static变量持有,也不具备static属性——和单例的核心区别是,单例用static变量持久化持有唯一实例,而这里的实例只是由静态方法创建,用完就失去引用,完全是普通实例。这些对象可以被垃圾回收
每次循环执行RandomSumBuilder.add().build()时,add()返回的实例仅在执行build()的瞬间被引用,build()返回int值后,这个实例就没有任何强引用指向它了。JVM的GC会自动标记这类无引用对象为可回收,后续在GC周期中清理,不会一直占用内存。不会给类加载器带来额外负担
类加载器只负责加载RandomSumBuilder类的字节码到JVM中,这个过程仅执行一次,无论创建多少该类的实例,类本身不会重复加载。创建实例是JVM堆内存的分配操作,和类加载器完全无关。你用VisualVM看不到类加载问题,正是因为类只加载了一次,后续都是实例化的内存操作。内部类实现建造者模式不会本质减少实例数量
普通内部类(非static)或static内部类的建造者,本质还是创建实例的逻辑,除非你在内部类中实现了单例/实例复用机制(比如枚举单例、实例池),否则和当前代码的实例创建量没有区别。是否减少实例数量取决于是否设计了复用逻辑,和是否用内部类无关。
内容的提问来源于stack exchange,提问作者Felix

