JVM从入门到入土之JVM的内存分配策略和垃圾回收器(下)

简介: 前言文本已收录至我的GitHub仓库,欢迎Star:github.com/bin39232820…种一棵树最好的时间是十年前,其次是现在

垃圾收集器


到Java8为止


Serial收集器

  • 历史悠久,jdk1.3虚拟机新生代唯一选择
  • 单线程收集器;进行GC工作时必须暂停其他所有的工作线程,直到他收集结束
  • 使用复制算法完成


  • 1.3-1.7不断追求GC停顿时间的缩短,而获取更好的体验

ParNew收集器

  • ParNew收集器是多线程版的Serial,除了使用多条线程进行垃圾回收外。其余行为包括Serial可用的所有参数、收集算法、StopTheWorld、对象分配规则、回收策略和Serial垃圾器一致。
  • 图解


  • 除了Serial之外,只有ParNew可以配合CMS工作
  • 也是新生代的收集器


Parallel Scavenge收集器

  • 也是一个新生代的收集器
  • 使用了复制算法同时又是一个并行的多线程收集器
  • CMS关于垃圾收集尽量缩短用户线程停止的时间;Parallel - Scavenge目的是达到一个可控制的目标吞吐量。(所谓吞吐量CPU用于运行用户代码时间和CPU总时间的比值。
  • 吞吐量 = 运行用户代码时间 / (运行用户代码时间和垃圾收集时间))
  • 停顿时间越短就越适合需要和用户交互的程序,良好的相应的速度可以提升用户体验。高吞吐量则可以最高效的利用CPU时间,尽快- 完成程序运算任务主要适合在后台运算而不是交互性的程序
  • Parallel Scavenge提供两个参数。-XX:MaxGCPauseMillis(最大垃圾收集停留时间) -XX:GCTimeRatio(设置吞吐量大小)
  • MaxGCPauseMillis:大于0的毫秒值。收集器将尽力保证最大垃圾收集停留时间不超过设定值。
  • GCTimeRatio:大于0小于100的值。默认值99。也就是垃圾收集时间占总时间的比例。吞吐量的倒数。
  • -XX:+UseAdaptiveSizePolicy:开关参数。开启后,无需手动指定新生代的大小、Eden、Survivor之间的比例等其他。又称“GC自适应- 调节”。自适应也是Parallel Scavenge和ParNew的一个重要区别。


Serial Old收集器

  • Serial Old是Serial收集器的老年代收集器。
  • 单线程收集器。标记 - 清理算法。
  • 主要意思在Client模式下虚拟机使用。
  • Server模式下:
  • JDK1.5之前版本配合Parallel Scavenge使用
  • CMS收集器的后备选择
  • 图解


Parallel Old收集器

  • 是Parallel Scavenge的老年代版本
  • 使用多线程和标记 - 清除算法
  • JDK1.6后才出现的
  • 图解


CMS收集器

  • 一种以获取最短回收停顿时间为目标的垃圾器
  • 基于标记 - 清除算法实现的
  • 实现细节:
  • 初始标记
  • 并发标记
  • 重新标记
  • 并发清除

其中,初始标记和重新标记还需stop the world。初始标记只是标记一下GC Roots能直接关联到的对象,速度很快。并发标记阶段也就是进行GC Roots Tracing的过程,而重新标记是为了修正 因用户程序继续运行导致标记变动的那一部分对象的标记记录。重新标记这段时间远比初始标记时间长但是远比并发标记时间短。


  • 优点:并发收集、低停顿
  • CMS收集器对CPU资源非常敏感;CMS收集器无法处理浮动垃圾,使其在标记过程失败,从而导致一次FullGC;CMS收集器是基于标记
  • 清除的算法,而其算法容易导致内存碎片。


G1收集器

  • 基于标记 - 清理算法,所以对长时间运行的系统,不会产生内存碎片。
  • 可以精准地控制停顿,既能让使用者明确指定在一个长度的时间M毫秒时间片段内,消耗在垃圾收集上的时间不得 超过N毫秒,这基本是Java(RTSJ)垃圾器的特征
  • G1收集器基本上不牺牲吞吐量的情况下完成低停顿的内存回收;G1将Java堆(新生代和老年代)划分成多个大大小小的独立区域, 然后进行区域回收


理解GC日志


图片

阅读

  • 最前面的数字:33.125 100.667 代表GC发生的时间;这个是从虚拟机启动来经过的秒数
  • GC和Full GC说明了此次垃圾收集停顿的类型。不是用来区分新生代GC还是老年代GC。如果有Full,则表明此次GC 发生了Stop The World
  • [DefNew [Tenured [Perm表示GC发生的区域 Serial收集器中的新生代名为Default New Generation所以显示[DefNew;如果是Parallel收集器新生代名称为 [ParNew 意为"Parallel New Generation";如果是Parallel Scavenge收集器新生代名称叫做PSYoungGen。老年代和永久代同理,根据垃圾收集器 的名称来决定的
  • 方括号内的“3324K --> 152K(3712K)”表示GC前的内存区域已使用容量 -> GC后内存区域已使用的容量(该内存区域总容量); 而方括号之外的3324K -> 152K(11904K)表示GC前Java堆已使用的容量->GC后Java堆已使用的容量(Java堆总容量)
  • 0.0025925 secs表示该内存区域GC所占时间,单位是秒。


垃圾收集器参数总结



内存分配和回收策略


Java自动内存管理主要解决了两个问题:

  • 给对象分配内存
  • 回收分配给对象的内存


对象优先在Eden分配

  • 大多数情况下,对象在Eden区中分配。当Eden内存不足时,虚拟机将发起一次MinorGC
  • 提供了-XX:+PrintGCDetails日志参数。告诉虚拟机发生垃圾回收时打印内存回收日志,并在线程结束后输出各内存的分配情况
  • Minor GC和Full GC(Stop The world)
  • 新生代GC(Minor GC):回收频繁,且回收速度较快
  • 老年代GC(Major GC或Full GC):经常至少伴随着一次Minor GC(并不是绝对,例如PS回收);Major GC速度至少比Minor GC速度慢10倍以上 示例
/**
 * VM args:-verbose:gc -Xms20M -Xmn10M -XX:SurvivorRatio=8 -XX:+PrintGCDetails
 */
public class TestAllocation {
    private static final int _1MB = 1024 * 1024;
    public static void main(String[] args) {
        byte[] allocation1, allocation2, allocation3, allocation4, allocation5;
        allocation1 = new byte[2 * _1MB];
        allocation2 = new byte[2 * _1MB];
        allocation3 = new byte[2 * _1MB];
        allocation3 = new byte[2 * _1MB];
        allocation4 = new byte[2 * _1MB];
        allocation5 = new byte[4 * _1MB];
        // 至少发生了MinorGC
    }
}
复制代码


[GC (Allocation Failure) [PSYoungGen: 7651K->1016K(9216K)] 7651K->2131K(19456K), 0.0017243 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[GC (Allocation Failure) [PSYoungGen: 7402K->1000K(9216K)] 8518K->8375K(19456K), 0.0039360 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[Full GC (Ergonomics) [PSYoungGen: 1000K->0K(9216K)] [ParOldGen: 7375K->8172K(24576K)] 8375K->8172K(33792K), [Metaspace: 3455K->3455K(1056768K)], 0.0103884 secs] [Times: user=0.16 sys=0.00, real=0.01 secs] 
Heap
 PSYoungGen      total 9216K, used 4373K [0x00000007bf600000, 0x00000007c0000000, 0x00000007c0000000)
  eden space 8192K, 53% used [0x00000007bf600000,0x00000007bfa457e0,0x00000007bfe00000)
  from space 1024K, 0% used [0x00000007bff00000,0x00000007bff00000,0x00000007c0000000)
  to   space 1024K, 0% used [0x00000007bfe00000,0x00000007bfe00000,0x00000007bff00000)
 ParOldGen       total 24576K, used 12268K [0x00000006c2800000, 0x00000006c4000000, 0x00000007bf600000)
  object space 24576K, 49% used [0x00000006c2800000,0x00000006c33fb3c8,0x00000006c4000000)
 Metaspace       used 3477K, capacity 4496K, committed 4864K, reserved 1056768K
  class space    used 375K, capacity 388K, committed 512K, reserved 1048576K
复制代码



大对象直接进入老年代

  • 所谓大对象:需要大量连续内存空间的Java对象。典型的如很长的字符串或数组(上面例子中byte[]就是大对象)
  • 大对象对虚拟机来说是坏事,尤其是那种朝生夕死的大对象。经常内存不足而导致不得不提前进行GC
  • 虚拟机提供-XX:PretenureSizeThreshold参数,使大于此值的对象在老年代分配。好处是避免了在Eden区及两个Survivor区间发生大量的复制。新生代采用复制算法进行垃圾收集。


长期存活的对象将进入老年代

  • 每个对象定义一个年龄(Age)计数器
  • 对象在Survivor区域每熬过一次MinorGC,年龄就会增加。默认年龄为15。当年龄超过一定的阈值就会进入老年代。 通过参数:-XX:MaxTenuringThreshold设置


动态对象年龄判断

为了更好的适应不同的程序,虚拟机并不是永远要求对象年龄达到MaxTenuringThreshold才晋升到老年代。 如果在Survivor空间的相同年龄所有对象大小的总和大于Survivor空间的一半,年龄大于或等于该年龄的对象可以直接进入老年代,无需MaxTenuringThreshold要求的对象


结尾


JVM的基本知识就这么多了,接下来我会给大家出一章面试题的章节,和一章根据业务实战JVM调优,大家持续关注哦

相关文章
|
8天前
|
Arthas 监控 Java
JVM进阶调优系列(9)大厂面试官:内存溢出几种?能否现场演示一下?| 面试就那点事
本文介绍了JVM内存溢出(OOM)的四种类型:堆内存、栈内存、元数据区和直接内存溢出。每种类型通过示例代码演示了如何触发OOM,并分析了其原因。文章还提供了如何使用JVM命令工具(如jmap、jhat、GCeasy、Arthas等)分析和定位内存溢出问题的方法。最后,强调了合理设置JVM参数和及时回收内存的重要性。
|
10天前
|
缓存 算法 Java
本文聚焦于Java内存管理与调优,介绍Java内存模型、内存泄漏检测与预防、高效字符串拼接、数据结构优化及垃圾回收机制
在现代软件开发中,性能优化至关重要。本文聚焦于Java内存管理与调优,介绍Java内存模型、内存泄漏检测与预防、高效字符串拼接、数据结构优化及垃圾回收机制。通过调整垃圾回收器参数、优化堆大小与布局、使用对象池和缓存技术,开发者可显著提升应用性能和稳定性。
32 6
|
6天前
|
Java Linux Windows
JVM内存
首先JVM内存限制于实际的最大物理内存,假设物理内存无限大的话,JVM内存的最大值跟操作系统有很大的关系。简单的说就32位处理器虽然可控内存空间有4GB,但是具体的操作系统会给一个限制,这个限制一般是2GB-3GB(一般来说Windows系统下为1.5G-2G,Linux系统下为2G-3G),而64bit以上的处理器就不会有限制。
8 1
|
9天前
|
存储 分布式计算 算法
1GB内存挑战:高效处理40亿QQ号的策略
在面对如何处理40亿个QQ号仅用1GB内存的难题时,我们需要采用一些高效的数据结构和算法来优化内存使用。这个问题涉及到数据存储、查询和处理等多个方面,本文将分享一些实用的技术策略,帮助你在有限的内存资源下处理大规模数据集。
19 1
|
10天前
|
存储 监控 Java
深入理解计算机内存管理:优化策略与实践
深入理解计算机内存管理:优化策略与实践
|
25天前
|
存储 算法 Java
聊聊jvm的内存结构, 以及各种结构的作用
【10月更文挑战第27天】JVM(Java虚拟机)的内存结构主要包括程序计数器、Java虚拟机栈、本地方法栈、Java堆、方法区和运行时常量池。各部分协同工作,为Java程序提供高效稳定的内存管理和运行环境,确保程序的正常执行、数据存储和资源利用。
46 10
|
25天前
|
存储 算法 Java
Java虚拟机(JVM)的内存管理与性能优化
本文深入探讨了Java虚拟机(JVM)的内存管理机制,包括堆、栈、方法区等关键区域的功能与作用。通过分析垃圾回收算法和调优策略,旨在帮助开发者理解如何有效提升Java应用的性能。文章采用通俗易懂的语言,结合具体实例,使读者能够轻松掌握复杂的内存管理概念,并应用于实际开发中。
|
1月前
|
存储 监控 算法
Java中的内存管理与垃圾回收机制解析
本文深入探讨了Java编程语言中的内存管理方式,特别是垃圾回收机制。我们将了解Java的自动内存管理是如何工作的,它如何帮助开发者避免常见的内存泄漏问题。通过分析不同垃圾回收算法(如标记-清除、复制和标记-整理)以及JVM如何选择合适的垃圾回收策略,本文旨在帮助Java开发者更好地理解和优化应用程序的性能。
|
1月前
|
存储 监控 算法
JVM调优深度剖析:内存模型、垃圾收集、工具与实战
【10月更文挑战第9天】在Java开发领域,Java虚拟机(JVM)的性能调优是构建高性能、高并发系统不可或缺的一部分。作为一名资深架构师,深入理解JVM的内存模型、垃圾收集机制、调优工具及其实现原理,对于提升系统的整体性能和稳定性至关重要。本文将深入探讨这些内容,并提供针对单机几十万并发系统的JVM调优策略和Java代码示例。
50 2
|
1月前
|
存储 Kubernetes 架构师
阿里面试:JVM 锁内存 是怎么变化的? JVM 锁的膨胀过程 ?
尼恩,一位经验丰富的40岁老架构师,通过其读者交流群分享了一系列关于JVM锁的深度解析,包括偏向锁、轻量级锁、自旋锁和重量级锁的概念、内存结构变化及锁膨胀流程。这些内容不仅帮助群内的小伙伴们顺利通过了多家一线互联网企业的面试,还整理成了《尼恩Java面试宝典》等技术资料,助力更多开发者提升技术水平,实现职业逆袭。尼恩强调,掌握这些核心知识点不仅能提高面试成功率,还能在实际工作中更好地应对高并发场景下的性能优化问题。