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 存储 算法
深入理解JVM,包含字节码文件,内存结构,垃圾回收,类的声明周期,类加载器
JVM全称是Java Virtual Machine-Java虚拟机JVM作用:本质上是一个运行在计算机上的程序,职责是运行Java字节码文件,编译为机器码交由计算机运行类的生命周期概述:类的生命周期描述了一个类加载,使用,卸载的整个过类的生命周期阶段:类的声明周期主要分为五个阶段:加载->连接->初始化->使用->卸载,其中连接中分为三个小阶段验证->准备->解析类加载器的定义:JVM提供类加载器给Java程序去获取类和接口字节码数据类加载器的作用:类加载器接受字节码文件。
759 55
|
9月前
|
Arthas 监控 Java
Arthas memory(查看 JVM 内存信息)
Arthas memory(查看 JVM 内存信息)
745 6
|
4月前
|
存储 缓存 NoSQL
工作 10 年!Redis 内存淘汰策略 LRU 和传统 LRU 差异,还傻傻分不清
小富带你深入解析Redis内存淘汰机制:LRU与LFU算法原理、实现方式及核心区别。揭秘Redis为何采用“近似LRU”,LFU如何解决频率老化问题,并结合实际场景教你如何选择合适策略,提升缓存命中率。
517 3
|
10月前
|
存储 分布式计算 监控
阿里云服务器实例经济型e、通用算力型u1、计算型c8i、通用型g8i、内存型r8i详解与选择策略
在阿里云现在的活动中,可选的云服务器实例规格主要有经济型e、通用算力型u1、计算型c8i、通用型g8i、内存型r8i实例,虽然阿里云在活动中提供了多种不同规格的云服务器实例,以满足不同用户和应用场景的需求。但是有的用户并不清楚他们的性能如何,应该如何选择。本文将详细介绍阿里云服务器中的经济型e、通用算力型u1、计算型c8i、通用型g8i、内存型r8i实例的性能、适用场景及选择参考,帮助用户根据自身需求做出更加精准的选择。
|
6月前
|
存储 人工智能 自然语言处理
AI代理内存消耗过大?9种优化策略对比分析
在AI代理系统中,多代理协作虽能提升整体准确性,但真正决定性能的关键因素之一是**内存管理**。随着对话深度和长度的增加,内存消耗呈指数级增长,主要源于历史上下文、工具调用记录、数据库查询结果等组件的持续积累。本文深入探讨了从基础到高级的九种内存优化技术,涵盖顺序存储、滑动窗口、摘要型内存、基于检索的系统、内存增强变换器、分层优化、图形化记忆网络、压缩整合策略以及类操作系统内存管理。通过统一框架下的代码实现与性能评估,分析了每种技术的适用场景与局限性,为构建高效、可扩展的AI代理系统提供了系统性的优化路径和技术参考。
344 4
AI代理内存消耗过大?9种优化策略对比分析
|
5月前
|
机器学习/深度学习 监控 安全
解密虚拟化弹性内存:五大核心技术与实施策略
本文深入解析虚拟化环境中实现内存弹性管理的五大核心技术与实施策略。内容涵盖内存架构演进、关键技术原理、性能优化方法及典型问题解决方案,助力提升虚拟机密度与资源利用率。
252 0
|
5月前
|
边缘计算 算法 Java
Java 绿色计算与性能优化:从内存管理到能耗降低的全方位优化策略与实践技巧
本文探讨了Java绿色计算与性能优化的技术方案和应用实例。文章从JVM调优(包括垃圾回收器选择、内存管理和并发优化)、代码优化(数据结构选择、对象创建和I/O操作优化)等方面提出优化策略,并结合电商平台、社交平台和智能工厂的实际案例,展示了通过Java新特性提升性能、降低能耗的显著效果。最终指出,综合运用这些优化方法不仅能提高系统性能,还能实现绿色计算目标,为企业节省成本并符合环保要求。
207 0
|
弹性计算 安全 数据库
【转】云服务器虚拟化内存优化指南:提升性能的7个关键策略
作为云计算服务核心组件,虚拟化内存管理直接影响业务系统性能表现。本文详解了内存优化方案与技术实践,助您降低30%资源浪费。
212 0
【转】云服务器虚拟化内存优化指南:提升性能的7个关键策略
|
10月前
|
缓存 监控 算法
JVM简介—2.垃圾回收器和内存分配策略
本文介绍了Java垃圾回收机制的多个方面,包括垃圾回收概述、对象存活判断、引用类型介绍、垃圾收集算法、垃圾收集器设计、具体垃圾回收器详情、Stop The World现象、内存分配与回收策略、新生代配置演示、内存泄漏和溢出问题以及JDK提供的相关工具。
JVM简介—2.垃圾回收器和内存分配策略
|
8月前
|
缓存 算法 Java
JVM深入原理(八)(一):垃圾回收
弱引用-作用:JVM中使用WeakReference对象来实现软引用,一般在ThreadLocal中,当进行垃圾回收时,被弱引用对象引用的对象就直接被回收.软引用-作用:JVM中使用SoftReference对象来实现软引用,一般在缓存中使用,当程序内存不足时,被引用的对象就会被回收.强引用-作用:可达性算法描述的根对象引用普通对象的引用,指的就是强引用,只要有这层关系存在,被引用的对象就会不被垃圾回收。引用计数法-缺点:如果两个对象循环引用,而又没有其他的对象来引用它们,这样就造成垃圾堆积。
218 0