new
指令时,首先将检查该指令的参数能否在常量池中定位到一个类的符号引用,检查引用代表的类是否已经被加载、解析和初始化,如果没有就执行类加载-XX:PetenureSizeThreshold
,若大于则直接分配到老年代,若不大于则判断新生代中Eden区(伊甸园)空间是否充足,若充足则分配到Eden区进行下一步,若不足则进行 Minor GC
新生代垃圾回收,然后分配内存空间。init
方法,初始化成员变量执行实例化代码块,调用类的构造方法,并把堆内对象的首地址赋值给引用变量。在类加载之后,就开始为对象分配内存,根据对象所需内存大小从堆中划分,分配方式主要有两种:指针碰撞和空闲列表
由此可见,选择哪种分配方式取决于堆空间是否规整,堆空间是否规整由垃圾回收器是否带有压缩整理功能决定。比如 Serial
、ParNew
垃圾回收期适用指针碰撞,CMS
垃圾回收器使用空闲列表。
创建对象是非常频繁的事情,如果不能保证线程安全,那么会很容易出现多线程操作同一块内存区域,因此保证线程安全对于虚拟机来说至关重要,虚拟机采用两种方式保证线程安全:
JVM优先使用TLAB进行对象内存分配,当对象所需内存大于TLAB剩余空间或TLAB内存耗尽时,才会采用CAS+重试进行内存分配。
当创建一个对象时,对象会被优先分配到新生代的 Eden 区。 JVM 会为对象定义一个对象年龄计数器(-XX:MaxTenuringThreshold
)
当 Eden 空间不足时,JVM 将执行新生代的垃圾回收(Minor GC)
Survivor
中,并且对象年龄 +1 Survivor
中同样也会经历 Minor GC
,每经历一次 Minor GC
,对象年龄都会+1,如果超过一定次数就会被放入老年代。如果分配的对象超过了-XX:PetenureSizeThreshold
,对象会直接被分配到老年代
在老年代中存放的对象当进行Major GC时被回收,或者Full GC时被回收。
JVM为Java程序提供自动内存管理,包括为对象分配内存和回收分配给对象的内存。而垃圾回收机制就是JVM回收分配给对象的内存的过程。
在系统运行的过程中,随着时间的推移一些对象会变成无用的,这些对象占据一定的内存,如果不及时对这些对象占用的内存进行回收再利用,内存会逐渐被耗尽。
垃圾回收机制包括三个方面:判断对象是否可回收、对可回收的对象进行回收、如何更好的进行垃圾回收
在进行垃圾回收时,首先需要判断一个对象是否可以被回收,有两种方法引用计数算法和可达性分析算法
JVM使用可达性分析算法判断对象是否可以被回收
GC Roots 对象都有哪些?
栈中引用的对象(包括虚拟机栈和本地方法栈)、方法区中类静态属性引用的对象、方法区中常量引用的对象
1、标记-清除
标记出所有不需要回收的对象,在标记完成后统一回收掉所有没有被标记的对象。
不足:
2、标记-复制
将内存划分为大小相等的两块,每次只使用其中一块,当这一块内存用完了就将还存活的对象复制到另一块上面,然后再把使用过的内存空间进行一次清理。
主要不足是只使用了内存的一半。
大多虚拟机采用这种收集算法来回收新生代,但是并不是将新生代划分为大小相等的两块,而是分为一块较大的 Eden 空间和两块较小的 Survivor 空间,每次使用 Eden 空间和其中一块 Survivor。在回收时,将 Eden 和 Survivor 中还存活着的对象一次性复制到另一块 Survivor 空间上,最后清理 Eden 和使用过的那一块 Survivor。
HotSpot 虚拟机的 Eden 和 两个Survivor 的大小比例默认为 8:1:1,保证了内存的利用率达到 90%。如果每次回收有多于 10% 的对象存活,那么一块 Survivor 空间就不够用了,此时需要依赖于老年代进行分配担保,也就是借用老年代的空间存储放不下的对象。
3、标记-整理
标记所有不需要回收的对象,让所有存活的对象都向一端移动,然后直接清理掉端边界以外的内存。
大多数情况下,对象在新生代 Eden 区分配,当 Eden 区空间不够时,发起 Minor GC。
大对象是指需要连续内存空间的对象,典型的大对象是那种很长的字符串以及数组。
经常出现大对象会提前触发垃圾收集以获取足够的连续空间分配给大对象。
-XX:PretenureSizeThreshold
,大于此值的对象直接在老年代分配,避免在 Eden 区和 Survivor 区之间的大量内存复制。
为对象定义年龄计数器,对象在 Eden 出生并经过 Minor GC 依然存活,将移动到 Survivor 中,年龄就增加 1 岁,增加到一定年龄则移动到老年代中。
-XX:MaxTenuringThreshold
用来定义年龄的阈值。
虚拟机并不是永远地要求对象的年龄必须达到 MaxTenuringThreshold
才能晋升老年代,如果在Survivor
中相同年龄所有对象大小的总和大于 Survivor
空间的一半,则年龄大于或等于该年龄的对象可以直接进入老年代,无需等到 MaxTenuringThreshold
中要求的年龄。
在发生 Minor GC
之前,虚拟机先检查老年代最大可用的连续空间是否大于新生代所有对象总空间,如果成立的话,那么Minor GC
可以确认是安全的。
如果不成立虚拟机会查看 HandlePromotionFailure
设置值是否允许担保失败,如果允许那么就会继续检查老年代最大可用的连续空间是否大于历次晋升到老年代对象的平均大小,如果大于,将尝试着进行一次 Minor GC
,如果小于或者 HandlePromotionFailure
设置不允许担保失败,那么就要进行一次 Full GC。
System.gc()
只是建议虚拟机执行 Full GC,但是虚拟机不一定真正去执行。不建议使用这种方式,而是让虚拟机管理内存。
老年代空间不足的常见场景为前文所讲的大对象直接进入老年代、长期存活的对象进入老年代等。
为了避免以上原因引起的 Full GC,应当尽量不要创建过大的对象以及数组。除此之外,可以通过-Xmn
虚拟机参数调大新生代的大小,让对象尽量在新生代被回收掉,不进入老年代。还可以通过-XX:MaxTenuringThreshold
调大对象进入老年代的年龄,让对象在新生代多存活一段时间。
使用复制算法的 Minor GC 需要老年代的内存空间作担保,如果担保失败会执行一次Full GC。
执行 CMS GC 的过程中同时有对象要放入老年代,而此时老年代空间不足(可能是 GC 过程中浮动垃圾过多导致暂时性的空间不足),便会报 Concurrent Mode Failure
错误,并触发 Full GC
。
Java System属性
和JVM命令行参数
;也可以动态的修改正在运行的 JVM 一些参数。当系统崩溃时,jinfo
可以从core文件里面知道崩溃的Java应用程序的配置信息。dump文件
,也可以查看堆内对象示例的统计信息、查看 ClassLoader
的信息
参考文章: