三、内存与垃圾回收

运行时数据区

在 JDK8 中方法区改为“元空间”,使用本地内存

程序计数器(线程私有)

每个线程都有一个独立的程序计数器,属于线程私有

作用是保存当前线程执行的字节码指令的地址或行号

虚拟机栈(线程私有)

每当一个方法被调用时,虚拟机会在栈中创建一个新的栈帧,该栈帧用于存储方法的对象引用、方法出口等信息。方法执行完毕后,栈帧会被弹出,释放内存

本地方法栈(线程私有)

与虚拟机栈类似,但为 JVM 调用到的本地(Native)方法服务

方法区(线程共享)

线程共享区域,存着类的结构信息、常量、静态变量

运行时常量池:

是方法区的一部分,用于存放编译期生成的各种字面量与符号引用,在运行期间也可以将新的常量放入池中

元空间:

JDK 8 之后改为元空间,使用本地内存,这使得方法区不再受JVM堆内存大小的直接限制

堆(线程共享)

存放几乎所有对象实例和数组。是垃圾收集器管理的主要区域

字符串常量池:

存放字符串常量

逃逸分析:

是 Java 编译器的优化技术,分析对象是否会逃逸出当前方法或线程的作用范围

如果对象不会逃逸,JVM 可以实现栈上分配、同步消除、标量替换等优化,减少内存分配开销和同步开销,从而提高应用的性能

垃圾回收

垃圾判断算法

引用计数算法

引用计数算法在对象头中分配空间来保存该对象被引用的次数。对象被引用时引用计数器 +1,反之,计数器 -1,当计数器归零时,该对象就被回收

无法解决循环依赖的问题

可达性分析算法

可达性分析算法通过 GC Roots 作为起点,然后向下搜索,搜索走过的路径被称为引用链,当一个对象到 GC Roots 之间没有任何引用相连时,即从 GC Roots 到该对象节点不可达,则证明该对象是需要垃圾收集的

GC Roots:一组必须活跃的引用,不是对象,它们是程序运行时的起点,是一切引用链的源头

解决了循环依赖的问题

Stop-The-World

在垃圾收集过程中,JVM 会暂停所有的用户线程,这种暂停被称为"Stop The World"事件

原因:防止在垃圾收集过程中,用户线程修改了堆中的对象,导致垃圾收集器无法准确地收集垃圾

垃圾收集算法

标记清除算法

先使用垃圾判断算法将对象标记,然后把垃圾清理掉,清理掉的垃圾就变成可使用的空闲空间,等待被再次使用

缺点:存在过多内存碎片,当需要分配较大的对象时,没有足够的连续空间会不得不触发新一轮的垃圾回收操作

复制算法

复制算法能够解决标记清除算法的内存碎片问题

1)将可用内存划分成大小相等的两块,每次只使用其中的一块

2)当这一块的内存用完了,就将还存活的对象复制到另外一块上面,然后再把已使用过的内存空间一次清理掉

优点:解决了内存碎片问题

缺点:实际使用的内存只有完整内存的一半

标记整理算法

使用垃圾判断算法标记后,让所有存活的对象都向一端移动,再清理掉端边界以外的内存区域

优点:解决了内存碎片与内存空间只能利用一半的问题

缺点:对象需要频繁变动,降低了效率

分代收集算法

1)根据对象存活周期将内存分为新生代和老年代,采用不同算法

2)新生代:选用复制算法

原因:每次垃圾收集时都有大批对象死去,只有少量存活。只需付出少量存活对象的复制成本就可以完成收集

3)老年代:标记清理算法 or 标记整理算法

原因:对象存活率高,没有额外空间进行复制

新生代与老年代

堆内存分为新生代与老年代,新生代又分为 Eden 区与 Survivor 区,Survivor 区又分为 From 与 To

垃圾回收机制

Minor GC:发生新生代的的垃圾收集,发生频繁,回收速度快

Major GC/Full GC:发生在老年代的垃圾收集,很少发生,速度慢

Eden 区

1)大多数情况下,对象会在新生代 Eden 区中进行分配,当 Eden 区没有足够空间进行分配时,JVM 会发起一次 Minor GC 进行清理

2)通过 Minor GC 之后,Eden 区中绝大部分对象会被回收,而存活对象将会进到 Survivor 的 From 区,若 From 区不够,则直接进入 To 区

Survivor 区

作为 Eden 区与 Old 区的缓冲

1)保证只有经历 16 次 Minor GC 还能在新生代中存活的对象,才会被送到老年代

2)Survivor 有 2 个区域,所以每次 Minor GC,会将之前 Eden 区和 From 区中的存活对象复制到 To 区域,每次清理时 From 与 To 会发生职责兑换(复制算法)

Old 区

占据着 2/3 的堆内存空间,只有在 Major GC 的时候才会进行清理,每次 GC 都会触发“Stop-The-World”内存越大,STW 的时间也越长

采用的是标记整理算法

存放对象:

1)长期存活对象:虚拟机定义了对象年龄计数器,在新生代中每经历一次 GC 会增长 1 岁,到达 15 岁后转移至 Old 区

2)大对象:占用大段连续空间的对象,无论存活时间多长都会放入 Old 区,避免在新生代中发生大量内存复制

垃圾收集器

CMS(分代收集器)

以最短停顿时间为目标的垃圾收集器,采用 “标记-清除”算法,在收集过程中尽可能让工作线程与垃圾收集线程并发执行,以减少停顿

过程:

1)初始标记:标记 GC Roots 能直接关联到的对象,耗时短但需要暂停用户线程

2)并发标记:从 GC Roots 能直接关联到的对象开始遍历整个对象图,耗时长但不需要暂停用户线程

3)重新标记:采用增量更新算法,对并发标记阶段因为用户线程运行而产生变动的那部分对象进行重新标记,耗时比初始标记稍长且需要暂停用户线程

4)并发清除:并发清除掉已经死亡的对象,耗时长但不需要暂停用户线程

G1(分区收集器)

G1 收集器是一种面向服务器的垃圾收集器,主要应用在多核 CPU 和 大内存的服务器环境中

G1 虽然也遵循分代收集理论,但使用 Region 根据不同的需求来扮演 Eden 空间、Survivor 空间、Old 空间、大对象区实现动态调整不同空间的大小

Region:把连续的 Java 堆划分为多个大小相等的独立区域

过程:

1)初始标记:标记 GC Roots 能直接关联到的对象,并且修改 TAMS 指针的值,让下一阶段用户线程并发运行时,能够正确的在 Reigin 中分配新对象

TAMS(Top at Mark Start)指针:每一个 Reigin 都有两个名为 TAMS 的指针,新分配的对象必须位于这两个指针以上位置表示存活,不会纳入回收范围

2)并发标记:从 GC Roots 能直接关联到的对象开始遍历整个对象图。遍历完成后,还需要处理 SATB 记录中变动的对象

SATB(snapshot-at-the-beginning,开始阶段快照):能够有效的解决并发标记阶段因为用户线程运行而导致的对象变动。其效率比 CMS 重新标记阶段更高

3)最终标记:暂停用户线程,处理并发阶段遗留下的少量 STAB 记录

4)筛选回收:按照各个 Region 的回收价值和成本进行排序,选择任意多个 Region 构成回收集。暂停用户线程,由多个收集线程并行执行,将回收集中 Region 存活对象复制到空 Region 中,再清理掉旧 Region

ZGC(分区收集器)

ZGC 也采用了复制算法,只不过做了重大优化,ZGC 在标记、转移和重定位阶段几乎都是并发的,核心技术依靠“指针染色”和“读屏障”

指针染色:在指针中添加额外信息,包括对象是否被移动、存活状态、是否被锁定等。使得 ZGC 操作更快,不需要做额外的内存访问

读屏障:读取对象时检查指针颜色,若对象发生移动则获取对象的新位置。这保证了在并发移动对象时的内存一致性

暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇