第二章 垃圾收集器与内存分配策略

简介: 一、前言 上一章节主要是Java内存分区与内存溢出的基础讲解,着篇主要讲解一些策略与GC等等,我的知识更新也是按照《深入理解Java虚拟机》来进行的,不足之处多鞭打。

一、前言

上一章节主要是Java内存分区与内存溢出的基础讲解,着篇主要讲解一些策略与GC等等,我的知识更新也是按照《深入理解Java虚拟机》来进行的,不足之处多鞭打。
     根据内存模型,虚拟机栈、本地方法、程序计数器都是线程私有的,在创建之初栈深度等已经确定,方法结束的时候就回收。所以主要讨论的是 Java堆与方法区(虽然JDK8没有方法了)。

二、垃圾回收与策略

2.1概述

Java堆中注意存储对象,我们关心的结果是对象是否已经“死去”。如下图JVM是通过是否可达Reachability Analysis分析。
提示:标记两次就“死亡”
Java 语言中,GC Root的对象有以下几种:
  • 虚拟机栈中(帧栈中的本地变量表)引用的对象
  • 方法区(1.8堆)中类静态属性引用的对象
  • 方法区(1.8堆)常量引用的对象
  • 方法的native(1.8本地内存)引用的对象
引用对象定义:
  • 强引用Strong Reference,主要存在就不会回收对象。
Object obj=new Object();
  • 软引用Soft Reference,一些有非必需的对象。内存溢之前进行软引用回收。
String value = new String(“sy”);
SoftReference sfRefer = new SoftReference (value );
sfRefer .get();//可以获得引用对象值
  • 弱引用weekReference是非必需对象,比软引用更弱。下一次内存回收,回收掉弱引用。
String value = new String(“sy”);
WeakReference weakRefer = new WeakReference(value );

System.gc();

weakRefer.get();//null
  • 虚引用PhantomReference,设置虚引用就是为了得到一个回收通知。

2.2 垃圾回收算法

    2.2.1 标记法-清除算法

标记需要回收的空间。缺点:是空间不连续,效率也很低下。

             2.2.2 复制算法

将内存分为两块,每次只使用其中一块,清理复制到另一块内存,一般采用这种算法(新生代对象%98“朝生夕死”)。缺点:内存变小
                 内存划分为Eden空间(大80%)、Sruvivor空间2块(小10%)。模式比例8:1;每次新生代为90%。

2.2.3 标记法-清除算法

所有存活对象向一端移动,清理另一端。

2.2.4 Hotspot算法实现

1、记录一下内存偏移量进入OopMap,记录点不能是全部的 全局性引用、执行上下文,应该是一些safepoiont ,这个安全点主要是 方法调用、循环跳转、异常跳转等。
2、虚拟机采用voluntary suspension 主动式中断,主动轮询到安全点,主动挂起进行枚举Gc root。
3、safe region 安全区域是安全点的扩展,离开安全区域进行枚举Gc Root。

2.3 垃圾收集器

2.3.1 Serial 收集器-串行

-新生代串行回收器

              (1)Serial特点:
			-单线程串行搜集
			-独占式垃圾回收
			-垃圾回收需要stop the world
			-复制算法
			-适合cpu等硬件不是很好的场合
	      (2)参数
		    -XX:+UseSerialGC  指定新生代使用串行搜集器

-老年代串行回收器

	(1)Serial old特点:
				-单线程、独占与新生代一样
				-标记整理算法
				-老年代回收时间比新生代时间更长,应用程序停顿更长
		 (2)设置参数:
				-XX:UseSerialGC 新生代,老年代都使用串行回收器
				-XX:UseParNeGC 新生代使用ParNew,老年代使用串行
				-XX:UseParalleGC 新生代使用ParallelGC回收,老年代使用串行回收


2.3.2 并行收集器



-新生代-ParNew回收器

		(1)特点:
			-串行回收变为多线程
			-使用复制方法
			-GC回收仍然会暂停,但是多线程的效率会提供(多核情况)
		(2)设置参数:
			-XX:UseParNewGc 新生代使用ParNew回收器,老年代使用串行Serial回收器
			-XX:UseConcMarksSweepGc 新生代使用ParNew,老年代使用CMS回收器
			-XX:ParallerGCThread=n 新生代回收使用线程数据量,cpu核数小于8的时候,等于cpu的数量,高于8时候,  3+((5*cpu_count)/8)

-新生代-Paraller Scavenge回收器

			(1)特点:
				-并行回收与ParNew一样,但是更注重吞吐量。
				-使用复制算法
				-支持自适应GC调节
			 (2)设置参数:
				-XX:UseParallerGC 新生代使用ParallerGC,老年代使用串行回收。
				-XX:UseParallerOldGC 新生代使用ParallelGC,老年代使用ParallelOld
				-XX:MaxGCPauseMillis=n  最大停顿时间
				-XX:GCTimeRatio=n (0-100之间)   设置n%的时间来进行垃圾回收
				-XX:UseAdaptiveSizePolicy  自动模式

-老年代ParallelOldGC 回收器

			(1)特点:
				-与新生代ParallelGC回收器一样,关注老年代的吞吐量。
				-使用标记-压缩算法
			(2)设置参数:
				-XX:UseParallelOldGC 新生代回收器,老年代使用ParallelOldGC回收器
				-XX:ParallelGCThreads=n cpu核与新生代一样

-老年代CMS回收器

		(1)特点:
			-“并发”运行回收
			-标记清除算法-有碎片
			-CMS主要关注系统停顿时间
			-用于大多数B/S服务器,默认线程数量(cpu_count+3)/4
		(2)主要步骤:
			-1初始化标记、2并发标记、3、预备清理 4、重新标记 5、并发清理 6、并发重置
                 (3) 设置参数:
			-XX:CMSPrecleaningEnabled 关闭预备清理
			-XX:UseConcMarksSweepGC  老年代使用CMS,新生代使用ParNew
			-XX:ConcGCThreads=n 设置并发线程数
			-XX:ParallelCMSTheads=n 设置并发线程数
			-XX:CMSInsitiaingOccupancyFeaction=n 1.7默认回收比例我老年代92%
			-XX:UseCMSCompactAtFullCollection 开启碎片整理,但是会增加停顿时间
			-XX:CMSFullGCsBeforeCompation=n  用于指定进行多少次CMS回收后, 再进行一次内存压缩 
  			-XX:+CMSParallelRemarkEnabled  在使用UseParNewGC 的情况下, 尽量减少 mark 的时间 
  			-XX:+UseCMSInitiatingOccupancyOnly  表示只有达到阀值时才进行CMS回收
			

-G1回收器

		替代CMS的垃圾回收器。

		(1)特点:
			-并行与并发
			-分代收集
			-空间整合
			-预测停顿

2.3 理解GC日志

[GC (System.gc()) [PSYoungGen: 9339K->800K(76288K)] 9339K->808K(251392K), 0.0014161 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[Full GC (System.gc()) [PSYoungGen: 800K->0K(76288K)] [ParOldGen: 8K->579K(175104K)] 808K->579K(251392K), 
[Metaspace: 3032K->3032K(1056768K)], 0.0049896 secs] [Times: user=0.02 sys=0.00, real=0.01 secs]

  • 1、“[GC” 与 “[Full GC”为这次垃圾回收的停顿类型,如果有Full表示发送了“stop-the-world”
  • 2、[GC (System.gc())为调用了System.gc()
  • 3、接下来表示垃圾收集器“[DefNew”=“Default New Generation”----》用的是serial收集器
  • "[Parnew"="Paraller New Genneration"---》用的是Parnew收集器
  • “[PsYoungGen”==》用的 是Parallel Scavenge 收集器
  • 3.1 紧接着的”9339K->800K(76288L)“=="GC前内存使用容量->GC后内存使用容量(该内存区域总容量)",
  • 4、方括号后面”9339->808K(251392)“=="GC前Java堆使用容量->GC后Java堆使用容量(Java堆总容量)"
  • 5、”0.0014161“==”该区域GC所占用的时间,单位为秒“
  • 6、"[Times: user=0.00 sys=0.00, real=0.00 secs]"=="用户消耗的CPU时间,内核消耗的CPU时间,真正从头到尾的时间"

2.4 垃圾回收常用参数

参  数  描  述
 UseSerialGC  虚拟机运行在Client 模式下的默认值,打开此开关后,使用Serial +Serial Old 的收集器组合进行内存回收
 
 UseParNewGC  打开此开关后,使用ParNew + Serial Old 的收集器组合进行内存回收
 UseConcMarkSweepGC  打开此开关后,使用ParNew + CMS + Serial Old 的收集器组合进行内存
回收。Serial Old 收集器将作为CMS 收集器出现Concurrent Mode Failure失败后的后备收集器使用
 UseParallelGC  虚拟机运行在Server 模式下的默认值,打开此开关后,使用Parallel
Scavenge + Serial Old(PS MarkSweep)的收集器组合进行内存回收
 UseParallelOldGC  打开此开关后,使用Parallel Scavenge + Parallel Old 的收集器组合进行内存回收
 SurvivorRatio  新生代中Eden 区域与Survivor 区域的容量比值, 默认为8, 代表
Eden :Survivor=8∶1
 PretenureSizeThreshold  直接晋升到老年代的对象大小,设置这个参数后,大于这个参数的对象
将直接在老年代分配
 MaxTenuringThreshold  晋升到老年代的对象年龄。每个对象在坚持过一次Minor GC 之后,年
龄就加1,当超过这个参数值时就进入老年代
 UseAdaptiveSizePolicy  动态调整Java 堆中各个区域的大小以及进入老年代的年龄
 HandlePromotionFailure  是否允许分配担保失败,即老年代的剩余空间不足以应付新生代的整个
Eden 和Survivor 区的所有对象都存活的极端情况
 ParallelGCThreads  设置并行GC 时进行内存回收的线程数
 GCTimeRatio  GC 时间占总时间的比率,默认值为99,即允许1% 的GC 时间。仅在

2.5内存分配与回收策略

2.5.1 对象优先在Eden 分配

实例代码:
private static  final int _1MB=1024*1024;

    /**
     * -Xms20M -Xmx20M -Xmn10M -XX:+PrintGCDetails -XX:SurvivorRatio=8
     * java堆大小为20M 10M分配给新生代 10M分配老年代
     * 新生代中国eden与suivivor区比例为8:1
     *  -发现新生代已经用了6M+souvivor@2M=8M,
     *  不够分配最后的4M,所以发生一次GC,老年代转移4M空间-40% 可以发现正确性
     */
    public static void testAllocation(){
        byte[] allocation1,allocation2,allocation3,allocation4;
        allocation1=new byte[2*_1MB];
        allocation2=new byte[2*_1MB];
        allocation3=new byte[2*_1MB];
        allocation4=new byte[4*_1MB];//出现一次minor gc 新生代gc
    }

    public static void main(String[] args) {
        testAllocation();
    }

GC信息:
[GC (Allocation Failure) [PSYoungGen: 6359K->818K(9216K)] 6359K->4922K(19456K), 0.0031997 secs] [Times: user=0.01 sys=0.00, real=0.00 secs] 
Heap
 PSYoungGen      total 9216K, used 7284K [0x00000007bf600000, 0x00000007c0000000, 0x00000007c0000000)
  eden space 8192K, 78% used [0x00000007bf600000,0x00000007bfc50508,0x00000007bfe00000)
  from space 1024K, 79% used [0x00000007bfe00000,0x00000007bfeccb90,0x00000007bff00000)
  to   space 1024K, 0% used [0x00000007bff00000,0x00000007bff00000,0x00000007c0000000)
 ParOldGen       total 10240K, used 4104K [0x00000007bec00000, 0x00000007bf600000, 0x00000007bf600000)
  object space 10240K, 40% used [0x00000007bec00000,0x00000007bf002020,0x00000007bf600000)
 Metaspace       used 3023K, capacity 4496K, committed 4864K, reserved 1056768K
  class space    used 329K, capacity 388K, committed 512K, reserved 1048576K

2.5.2 大对象直接进入老年代

实例代码:
package com.ycy.java.gc;

/**
 * @Copyright © 2017 . All rights reserved. Created with IntelliJ IDEA.
 * @project: untitled
 * @Package: com.ycy.java.gc
 * @Description:
 * @autho: ycy
 * @Date: 2017-10-19
 * @Time: 23:13
 */
public class TestGc {
    private static  final int _1MB=1024*1024;

    /**
     * vgs:  -Xms20M -Xmx20M -Xmn10M -XX:+PrintGCDetails -XX:SurvivorRatio=8 -XX:PretenureSizeThreshold=3145700
     * object space 10240K, 60% 直接进入老年代,因为大于设置的3145700
     * Server模式下默认:Serial old+Parallel Scavenger
     * -XX:PretenureSizeThreshold=3145700不认识这个参数,但是一样大对象进入老年区 
     */
    public static  void testPretenureSizeThreshold(){
        byte[] allocation;
        allocation=new byte[6*_1MB];//老年代中
    }

    public static void main(String[] args) {
        //对象优先分配Eden
//        testAllocation();
        //大对象直接进入老年代
        testPretenureSizeThreshold();
    }
}

GC信息:
Heap
 PSYoungGen      total 9216K, used 2450K [0x00000007bf600000, 0x00000007c0000000, 0x00000007c0000000)
  eden space 8192K, 29% used [0x00000007bf600000,0x00000007bf864bb8,0x00000007bfe00000)
  from space 1024K, 0% used [0x00000007bff00000,0x00000007bff00000,0x00000007c0000000)
  to   space 1024K, 0% used [0x00000007bfe00000,0x00000007bfe00000,0x00000007bff00000)
 ParOldGen       total 10240K, used 6144K [0x00000007bec00000, 0x00000007bf600000, 0x00000007bf600000)
  object space 10240K, 60% used [0x00000007bec00000,0x00000007bf200010,0x00000007bf600000)
 Metaspace       used 3022K, capacity 4496K, committed 4864K, reserved 1056768K
  class space    used 329K, capacity 388K, committed 512K, reserved 1048576K

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

  如果对象在Eden出生到一次Minor GC新生代GC之后仍然存活,并且在Survivor中容纳,移到Survivor,这个时候年龄1岁,默认阀值为15岁,就会移动到老年代。

示例代码:
    /**
     * -Xms20M -Xmx20M -Xmn10M -XX:+PrintGCDetails -XX:SurvivorRatio=8  -XX:MaxTenuringThreshold=0
     * 修改-XX:MaxTenuringThreshold=15 观察对象存在新生代
     */
    public static  void testTenuringThreshold(){
        byte[] allocation1,allocation2,allocation3,
                allocation4,allocation5,allocation6;
        allocation1=new byte[1*_1MB];
        allocation2=new byte[1*_1MB];
        allocation3=new byte[1*_1MB];
        allocation4=new byte[1*_1MB];
        allocation5=new byte[1*_1MB];
        allocation5=null;
        allocation5=new byte[1*_1MB];
    }

GC结果 :XX:MaxTenuringThreshold=0
from space 1024K, 0% used [0x00000007bfe00000,0x00000007bfe00000,0x00000007bff00000)
GC结果 :XX:MaxTenuringThreshold=15
from space 1024K, 75% 

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

相同年龄对象内存之和大于Survivor的一半,年龄大于或者等于该年龄的对象,直接进入老年代。
上面代码蒋年龄限制设置1-15之间,你会发现,对象直接进入老年代。
  object space 10240K, 55% used 

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

1.6之后,老年代的连续空间大于新生代的对象的总大小,或者历次晋升的平均大小,进行Minor GC ,否则full GC。意思就是老年代大于新生代或进行一次Minor GC。

三、总结

 主要讲解垃圾回收器的特点和运作原理,验证了一些虚拟机 分配原则。








目录
相关文章
|
11月前
|
存储 分布式计算 监控
阿里云服务器实例经济型e、通用算力型u1、计算型c8i、通用型g8i、内存型r8i详解与选择策略
在阿里云现在的活动中,可选的云服务器实例规格主要有经济型e、通用算力型u1、计算型c8i、通用型g8i、内存型r8i实例,虽然阿里云在活动中提供了多种不同规格的云服务器实例,以满足不同用户和应用场景的需求。但是有的用户并不清楚他们的性能如何,应该如何选择。本文将详细介绍阿里云服务器中的经济型e、通用算力型u1、计算型c8i、通用型g8i、内存型r8i实例的性能、适用场景及选择参考,帮助用户根据自身需求做出更加精准的选择。
|
5月前
|
存储 缓存 NoSQL
工作 10 年!Redis 内存淘汰策略 LRU 和传统 LRU 差异,还傻傻分不清
小富带你深入解析Redis内存淘汰机制:LRU与LFU算法原理、实现方式及核心区别。揭秘Redis为何采用“近似LRU”,LFU如何解决频率老化问题,并结合实际场景教你如何选择合适策略,提升缓存命中率。
693 3
|
7月前
|
存储 人工智能 自然语言处理
AI代理内存消耗过大?9种优化策略对比分析
在AI代理系统中,多代理协作虽能提升整体准确性,但真正决定性能的关键因素之一是**内存管理**。随着对话深度和长度的增加,内存消耗呈指数级增长,主要源于历史上下文、工具调用记录、数据库查询结果等组件的持续积累。本文深入探讨了从基础到高级的九种内存优化技术,涵盖顺序存储、滑动窗口、摘要型内存、基于检索的系统、内存增强变换器、分层优化、图形化记忆网络、压缩整合策略以及类操作系统内存管理。通过统一框架下的代码实现与性能评估,分析了每种技术的适用场景与局限性,为构建高效、可扩展的AI代理系统提供了系统性的优化路径和技术参考。
482 4
AI代理内存消耗过大?9种优化策略对比分析
|
6月前
|
机器学习/深度学习 监控 安全
解密虚拟化弹性内存:五大核心技术与实施策略
本文深入解析虚拟化环境中实现内存弹性管理的五大核心技术与实施策略。内容涵盖内存架构演进、关键技术原理、性能优化方法及典型问题解决方案,助力提升虚拟机密度与资源利用率。
316 0
|
6月前
|
边缘计算 算法 Java
Java 绿色计算与性能优化:从内存管理到能耗降低的全方位优化策略与实践技巧
本文探讨了Java绿色计算与性能优化的技术方案和应用实例。文章从JVM调优(包括垃圾回收器选择、内存管理和并发优化)、代码优化(数据结构选择、对象创建和I/O操作优化)等方面提出优化策略,并结合电商平台、社交平台和智能工厂的实际案例,展示了通过Java新特性提升性能、降低能耗的显著效果。最终指出,综合运用这些优化方法不仅能提高系统性能,还能实现绿色计算目标,为企业节省成本并符合环保要求。
253 0
|
弹性计算 安全 数据库
【转】云服务器虚拟化内存优化指南:提升性能的7个关键策略
作为云计算服务核心组件,虚拟化内存管理直接影响业务系统性能表现。本文详解了内存优化方案与技术实践,助您降低30%资源浪费。
284 0
【转】云服务器虚拟化内存优化指南:提升性能的7个关键策略
|
11月前
|
机器学习/深度学习 存储 PyTorch
PyTorch内存优化的10种策略总结:在有限资源环境下高效训练模型
在大规模深度学习模型训练中,GPU内存容量常成为瓶颈,特别是在训练大型语言模型和视觉Transformer时。本文系统介绍了多种内存优化策略,包括混合精度训练、低精度训练(如BF16)、梯度检查点、梯度累积、张量分片与分布式训练、
541 14
PyTorch内存优化的10种策略总结:在有限资源环境下高效训练模型
|
11月前
|
缓存 监控 算法
JVM简介—2.垃圾回收器和内存分配策略
本文介绍了Java垃圾回收机制的多个方面,包括垃圾回收概述、对象存活判断、引用类型介绍、垃圾收集算法、垃圾收集器设计、具体垃圾回收器详情、Stop The World现象、内存分配与回收策略、新生代配置演示、内存泄漏和溢出问题以及JDK提供的相关工具。
JVM简介—2.垃圾回收器和内存分配策略
|
机器学习/深度学习 计算机视觉
RT-DETR改进策略【卷积层】| CVPR-2023 部分卷积 PConv 轻量化卷积,降低内存占用
RT-DETR改进策略【卷积层】| CVPR-2023 部分卷积 PConv 轻量化卷积,降低内存占用
443 13
RT-DETR改进策略【卷积层】| CVPR-2023 部分卷积 PConv 轻量化卷积,降低内存占用
|
机器学习/深度学习 编解码 BI
RT-DETR改进策略【Conv和Transformer】| CVPR-2023 BiFormer 稀疏自注意力,减少内存占用
RT-DETR改进策略【Conv和Transformer】| CVPR-2023 BiFormer 稀疏自注意力,减少内存占用
368 0
RT-DETR改进策略【Conv和Transformer】| CVPR-2023 BiFormer 稀疏自注意力,减少内存占用