深入理解 Java 虚拟机
JVM 堆内存分布
JVM 堆内存分布主要可以分为几个区域,每个区域有其特定的用途和特点:
1. 堆内存结构
JVM 堆内存通常分为以下几个主要区域:
- 年轻代(Young Generation) :用于存放新创建的对象,通常又分为三个部分:
- Eden 区 :新对象的默认分配区域。
- Survivor 区(S0 和 S1) :用于存放经过一次垃圾回收后仍然存活的对象。Survivor 区之间会轮流使用。
- 老年代(Old Generation) :用于存放经历多次垃圾回收仍然存活的对象。这个区域的对象通常较大且生命周期较长。
- 永久代(PermGen)或元空间(Metaspace) :存放类的元数据(如类的结构、方法、常量池等)。在 Java 8 及之后,永久代被元空间取代,元空间使用本地内存,不再受限于堆内存。
2. 内存配置
JVM 堆内存的大小可以通过启动参数进行配置:
-Xms
:设置初始堆内存大小。-Xmx
:设置最大堆内存大小。
例如, -Xms512m -Xmx1024m
表示初始堆内存为 512MB,最大堆内存为 1024MB。
3. 垃圾回收策略
不同的垃圾回收器会采用不同的策略来回收堆内存:
- Serial GC :适合小型应用,使用单线程进行垃圾回收。
- Parallel GC :使用多线程进行年轻代的垃圾回收,适合多核处理器。
- CMS(Concurrent Mark-Sweep) :尝试减少停顿时间,通过并发标记和清除。
- G1(Garbage-First) :以垃圾回收效率为目标,适合大内存应用,能够在可控的停顿时间内进行回收。
4. 堆内存的使用和监控
可以使用工具如 JVisualVM、JConsole 和各种 Profiler 来监控 JVM 堆内存的使用情况,分析内存泄漏和性能问题。
希望这些信息能帮助你更好地理解 JVM 堆内存的分布!如果有特定的方面想深入了解,随时告诉我!
JVM 堆内存分配策略
分新生代、老年代、永久代(jdk 1.8 之后替换成了 Metaspace)
- 新生代 【8:1:1】
- Eden 区+Survivor1 区+Survivor2 区,Eden 和 Survivor 默认 8:1,两块 Survivor 一样大(读完本书可以知道是因为新生代多采取复制清除 GC 算法)
- 对象优先在 Eden 分配
- 老年代 【新生代:老年代=1:2】
- 大对象直接放入老年代
- 长期存活的对象晋入老年代
动态对象年龄判断:对象最新进入 Eden 区,进过第一次 MinorGC 后如果该对象任然存活,移入 Survivor 区,并维护一个年龄计数器,设为 1。后续如果再次经过一次 MinorGC,还没死亡的话年龄计数器自增 1,如果年龄超过 15,自动晋入老年代。或者相同年龄的对象超过 Survivor 内存区域一般时,也会自动把这些对象移入老年代
- 永久代在 jdk 1.8 中被 Metaspace 元空间取代
- 参考: Java8 内存模型—永久代(PermGen) 和元空间(Metaspace)
- 可由**-XX:MaxPermSize**进行设置,永久代也称为方法区 ,用来存放类的信息、常量池、方法数据、方法代码等
- 元空间不再是存放在虚拟机中了,而是直接使用本地内存
永久代也是会发生垃圾回收的,但有三点条件:
- 该类的实例都被回收。
- 加载该类的 classLoader 已经被回收
- 该类不能通过反射访问到其方法,而且该类的 java.lang.class 没有被引用
满足这三个条件可以回收,但回不回收还得看 jvm,但很少说永久代垃圾回收,垃圾回收侧重新生代、永久代
堆内存各区域比例及相关控制参数
总的来说,堆可分为新生代+老年代,可由参数**–Xms(初始堆大小)、-Xmx(最大堆大小) 来控制大小,新生代大小由参数 -Xmn 控制,新生代三块分区比例可由 –XX:SurvivorRatio 控制,新生代和老年代比例可由参数 –XX:NewRatio**控制
如何判断对象已死亡?
引用计数法
最容易想到的就是这种,每有一个引用指向对象,引用计数器就加一,引用失效就减一。一旦引用计数器为 0,表明对象已死亡,可回收。
问题是如果两个对象相互引用,引用计数器始终不会为 0
可达性分析算法
提出一个 GC Roots 对象的概念,GC Roots 为起点向下搜索,所走过的路径为引用链,如果没有 GC Roots 对象到该对象的引用链,表明该对象可回收
- GC Roots 对象包含哪些?
- JVM 虚拟机栈中引用的对象(栈帧是对象调用方法时生成的,具体说是局部变量表中引用的对象)
- 本地方法栈中的 JNI 引用的对象(Java Native Interface 缩写,Native 方法是 jav 调用其他语言的方法,多用来提高效率、和底层硬件、小做系统交互)
- 方法区中静态变量、常量引用的对象
如图,像对象实例 1、2、4、6 是 GC Roots 可达的,3 和 5 是可被回收的
这里有个问题是,搜索时是采用广度优先还是深度优先搜索?搞不清
什么时候会进行垃圾回收 GC?
垃圾回收不能像 C++似的手动进行,java 中是由 JVM 自动判断进行 GC 的,但 System.gc() 可以提示进行 GC
- 新生代内存空间不足时,会进行 Minor GC
- 长期存活的对象由新生代晋升入老年代时,老年代内存空间不足的话也会进行 full GC
看下对象的引用
引用分为四种:强引用、软引用、弱引用、虚引用
- 强引用:强引用存在的话,即使内存不够,也不会对强引用对象回收。
P p = new P()
就是强引用 - 软引用:内存空间足够时,不会回收;不足时才会对其回收。 可用来实现内存敏感的高速缓存
- 弱引用:垃圾回收器扫描到弱引用对象时就会对其回收,无论内存空间足不足
- 虚引用:“虚”字可知,表明该引用可有可无。该虚引用对象被垃圾回收器回收时候,系统能够接收到一个通知
GC 算法有哪些?
- 标志-清除法
- 适合老年代
- 问题:标记和清除效率不高、内存空间碎片化的问题
- 标志-整理法
- 适合老年代
- 也是为了解决碎片问题,标志同上,但会将所有存活的对象向一端移动,再回收
- 复制清除法
- 适合新生代
- 两块一样大小的内存,每次用一块,这也是它的缺点(内存只使用一半)
- 每次只对一块内存空间回收,效率高,不会出现空间碎片
- 分代收集法 【用的最多的 GC 算法】
- 根据新生代和老年代对象的性质,采取新生代使用复制清除法,老年代使用标志-清除/整理法
- 新生代会有大量对象死亡,所以 Minor GC 发生频率高,而老年代中对象存活率高,GC 没那么频繁
垃圾回收器
垃圾回收器(Garbage Collector,GC)是 JVM 的重要组成部分,负责自动管理内存,回收不再使用的对象,以防止内存泄漏和优化性能。以下是几种主要的垃圾回收器及其特点:
1. Serial GC
- 描述 :单线程垃圾回收器,适用于小型应用。
- 特点 :简单,易于实现。停顿时间较长,因为它在回收过程中会暂停所有应用线程。
2. Parallel GC
- 描述 :多线程垃圾回收器,适用于多核处理器。
- 特点 :在年轻代的回收中使用多个线程,提高回收效率。通过使用多个线程缩短停顿时间。
3. CMS (Concurrent Mark-Sweep) GC
- 描述 :旨在减少停顿时间的垃圾回收器。
- 特点 :
- 使用并发标记和清除的方式,允许应用线程在大部分回收过程中继续运行。
- 在标记阶段,标记所有存活的对象;在清除阶段,清除未标记的对象。
- 停顿时间较短,但可能导致碎片问题。
4. G1 (Garbage-First) GC
- 描述 :适用于大内存应用的低延迟垃圾回收器。
- 特点 :
- 将堆分成多个小块(Region),并优先回收垃圾最多的区域。
- 通过控制停顿时间,优化应用性能。
- 支持并行回收和并发标记。
5. ZGC (Z Garbage Collector)
- 描述 :低延迟垃圾回收器,适合大内存应用。
- 特点 :
- 支持大规模堆内存(可达数 TB)。
- 在垃圾回收过程中,几乎不产生停顿时间,停顿时间通常在毫秒级。
6. Shenandoah GC
- 描述 :类似于 ZGC 的低延迟垃圾回收器。
- 特点 :
- 采用并发的方式进行标记和清除,降低停顿时间。
- 适合大内存应用,能够实现较短的停顿时间。
GC 策略选择
选择合适的垃圾回收器取决于具体应用的需求,通常需要在停顿时间、吞吐量和内存占用之间进行权衡。
监控和调优
可以使用 JVM 参数和工具来监控和调优垃圾回收器的行为,例如使用 -XX:+PrintGC
打印 GC 日志,或者使用工具如 VisualVM 和 JConsole 分析内存使用情况。
如果你对某个特定的垃圾回收器或调优策略感兴趣,可以进一步深入讨论!
补充
看到一篇博客提到了死亡对象如何搜索 BFS、DFS,有点兴趣, 见此
为什么新生代用广度搜索,老生代用深度搜索?
深度优先 DFS 一般采用递归方式实现,处理 tracing 的时候,可能会导致栈空间溢出,所以一般采用广度优先来实现 tracing(递归情况下容易爆栈)。
广度优先的拷贝顺序使得 GC 后对象的空间局部性(memory locality)变差(相关变量散开了)。
广度优先搜索法一般无回溯操作,即入栈和出栈的操作,所以运行速度比深度优先搜索算法法要快些。
深度优先搜索法占内存少但速度较慢,广度优先搜索算法占内存多但速度较快。结合深搜和广搜的实现,以及新生代移动数量小,老生代数量大的情况,我们可以得到了解答。
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
下一篇: Java 懒汉式单例模式
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论