new 触发分配内存、调用构造函数、返回引用三步原子操作,任一失败则整体失败;它不等于创建对象,而是启动对象实例化全过程。

new 关键字到底在干啥
它不是“创建对象”,而是触发三步原子操作:分配堆内存 → 调用构造函数 → 返回引用。中间任何一步失败(比如内存不足、构造函数抛异常),new 就直接失败,不会留下半成品对象。
常见错误现象:OutOfMemoryError: Java heap space 不一定发生在大数组分配时,也常出现在频繁 new 小对象但没及时 GC 的场景;构造函数里隐式调用 this() 或 super() 失败,会掩盖真实异常栈。
- 构造函数里别做耗时操作(如 IO、网络请求),否则阻塞线程且难诊断
-
new后的对象引用指向堆中地址,局部变量只是栈里的指针副本,别误以为“赋值=复制对象” - JVM 参数
-Xmx控制堆上限,但new分配的是连续内存块,碎片多时即使总空间够也会失败
类加载完成 ≠ 可以 new
类必须完成初始化(<clinit></clinit> 方法执行完)才能 new 实例。如果类静态块里有死循环、锁竞争或依赖未就绪的资源,new 会卡在类初始化阶段,表现为线程 BLOCKED,堆栈停留在 java.lang.Class.forName 或 java.lang.ClassLoader.loadClass。
- 避免在
static块里调用外部服务或读取未保证存在的配置文件 - 使用
ClassLoader.getSystemClassLoader().loadClass("X")只加载不初始化,而Class.forName("X")默认会初始化——这点影响new的时机 - Spring 等框架代理类可能绕过直接
new,比如@Scope("prototype")bean 仍由容器管理生命周期,手动new会导致依赖未注入
堆内存分配不是每次都走 Eden 区
HotSpot JVM 对 new 的优化很激进:小对象、逃逸分析通过、方法内联后,JIT 可能直接栈上分配(Scalar Replacement),根本不进堆;大对象(默认 > 512KB)则直接进老年代(Old Gen),跳过 Young GC 流程。
这意味着你看到的 “对象在堆里” 是逻辑视图,实际内存位置由运行时决策。用 jstat -gc 观察 EC(Eden Capacity)和 OC(Old Capacity)变化,比看代码更准。
-
-XX:+DoEscapeAnalysis默认开启,但方法内联深度不够(-XX:MaxInlineLevel)会导致逃逸分析失效 - 对象大小估算要算上对齐填充(padding),
Object实际占 16 字节(Mark Word + Class Pointer),不是 8 字节 - 使用
Unsafe.allocateInstance()可绕过构造函数分配内存,但对象处于未初始化状态,字段全为零值——这不是new的替代方案,是底层 hack
为什么 new 出来的对象有时 == 有时 !=
== 比较的是堆地址,不是内容。两个 new String("a") 一定 !=,因为每次 new 都分配新内存;但 new Integer(127) 和 Integer.valueOf(127) 可能 ==,因为后者用了缓存池(-128 ~ 127),前者没走缓存。
- 字符串字面量(
"abc")存在字符串常量池,new String("abc")强制堆分配,再调.intern()才可能复用池中实例 - 包装类缓存范围可配置(如
-Djava.lang.Integer.IntegerCache.high=200),但new永远不查缓存 - 自定义类别重写
equals却忘了hashCode,会导致HashMap查不到new出来的对象——这不是new的问题,是契约没守牢
堆分配细节藏得深,但逃逸分析是否生效、对象大小是否触发 TLAB 分配、GC 算法对内存布局的要求,这些都会让同一段 new 代码在不同 JVM 参数或负载下行为不同。别只盯着语法,得看运行时上下文。








