JVM基础(3)——JVM垃圾回收机制

作者简介:大家好,我是smart哥,前中兴通讯、美团架构师,现某互联网公司CTO

联系qq:184480602,加我进群,大家一起学习,一起进步,一起对抗互联网寒冬

学习必须往深处挖,挖的越深,基础越扎实!

阶段1、深入多线程

阶段2、深入多线程设计模式

阶段3、深入juc源码解析

阶段4、深入jdk其余源码解析

阶段5、深入jvm源码解析

一、简介

我们在 JVM内存模型一章中,介绍了JVM中的Java堆内存区域。该区域是JVM为Java对象分配内存的主要区域,本章我们主要针对该块区域讲解JVM的垃圾回收机制。

我们先通过一个示例,回顾下对象的分配与引用:

    public class Kafka {
        public static void main(String[] args) {
            loadReplicaFromDisk();
        }
    
        private static void loadReplicaFromDisk() {
            ReplicaManager replicaManager = new ReplicaManager();
            replicaManager.load();
        }
    }

首先,main线程会执行main()方法里面的代码,它会把main()方法的栈帧压入自己的Java虚拟机栈中:

接着,main()方法内部调用了loadReplicaFromDisk()方法,就会创建一个loadReplicaFromDisk()方法对应的栈帧,同时在栈帧中存入一个局部变量replicaManager:

接着,loadReplicaFromDisk()方法内部创建了一个ReplicaManager对象,此时就会在Java堆内存中为该实例对象分配内存空间,并让局部变量replicaManager指向该实例对象:

最后,执行实例对象的load()方法,完成我们的业务逻辑。那么,这里思考一个问题,load方法执行完毕后会发生什么?我们上一章中提到过,方法执行完毕后对应的栈帧就会出栈,而一旦栈帧出栈,“replicaManager”局部变量也就没有了,此时就没有任何一个变量指向Java堆内存里的ReplicaManager实例对象了:

Java堆内存里面的资源是有限的,所以对于上述ReplicaManager这类孤立的对象,必须要有一种机制去清理它们,JVM会有一个在后台运行的垃圾回收线程,去分析和处理这些垃圾对象,这就是本章要讲的 JVM垃圾回收机制 。

二、分代回收

在正式讲解JVM垃圾回收机制前,我们先来了解下JVM内存分代模型、: 新生代 、 老年代 、 永久代 。

2.1 新生代

还是通过一个代码示例来讲解:

    public class Kafka {
        public static void main(String[] args) throws InterruptedException {
            while (true){
                loadReplicaFromDisk();
                Thread.sleep(1000);
            }
        }
    
        private static void loadReplicaFromDisk() {
            ReplicaManager replicaManager = new ReplicaManager();
            replicaManager.load();
        }
    }

上述代码中,当main线程执行main()方法后,会进入一个永久循环,执行完第一遍后,JVM中的数据结构大致如下图:

当进入下一次循环再执行loadReplicaFromDisk()时,会再构造一个ReplicaManager对象实例放入Java堆内存中,原来的那个就会被JVM垃圾回收线程给回收掉,如此循环往复,ReplicaManager对象的生命周期非常短,频繁地被创建和回收:

像ReplicaManager这种“朝生暮死”的小对象,通常都会在Java堆内存区域的"新生代"进行分配。但是,并不是每次JVM都会进行回收,默认情况下当新生代的内存空间快被占满时,会触发一次 “Minor GC” ,此时才会进行回收。

2.2 老年代

我们对2.1中的代码进行改造,此时fetcher是一个静态变量,其实例对象ReplicaFetcher会一直被该静态变量引用,而ReplicaManager对象则一直“朝生暮死”:

    public class Kafka {
        private static ReplicaFetcher fetcher = new ReplicaFetcher();
    
        public static void main(String[] args) throws InterruptedException {
            loadReplicaFromDisk();
            while (true) {
                fetcheReplicaFromRemote();
                Thread.sleep(1000);
            }
        }
    
        private static void loadReplicaFromDisk() {
            ReplicaManager replicaManager = new ReplicaManager();
            replicaManager.load();
        }
    
        private static void fetcheReplicaFromRemote() {
            fetcher.fetch();
        }
    }

最初时,ReplicaFetcher对象和ReplicaManager对象都被分配在新生代:

根据Java虚拟机规范, 如果一个实例对象在新生代中,成功的在15次垃圾回收之后,还是没有被回收到,那么就会被转移到老年代 ,所以ReplicaFetcher这个对象会首先在“年轻代”驻留一会儿,但是最终会进入老年代:

而ReplicaManager对象,当loadReplicaFromDisk()方法执行完成后,栈帧就会出栈,所以年轻代里的ReplicaManager会被垃圾回收线程清理掉:

老年代也有类似"Minor GC"的机制,另外,对于一些大对象,会直接在“老年代“分配;在一次”Minor GC“之后,如果新生代中的存活对象过多,即使这些对象年龄没有达到15,也会直接进入老年代。这些细节,后面我们会专门讲解。

2.3 永久代

永久代就是我们在JVM内存模型中提到到“方法区”,方法区中存储着类的信息,当满足以下三个条件时,方法区里的类会被回收:

  • 该类的所有实例对象已经从Java堆内存中被回收;
  • 加载这个类的ClassLoader已经被回收;
  • 对该类的Class对象没有任何引用。

三、JVM参数

3.1 核心参数

先来看一些核心的参数,通过这些参数可以设置上述提到的新生代、老年代、永久代的内存区域大小:

-Xms :Java堆内存区域的大小;

-Xmx :Java堆内存区域的最大大小;

-Xmn :Java堆内存区域的新生代大小,扣除新生代剩下的就是老年代的大小;

-XX:PermSize :永久代大小;

-XX:MaxPermSize :永久代的最大大小;

-Xss :每个线程的栈内存大小。

-Xms和-Xmx,分别用于设置Java堆内存的初始大小和最大大小,一般设置为相同值就可以,这样就限定了Java堆内存的总大小。

同理,-XX:PermSize和-XX:MaxPermSize配合使用可以限定永久代的大小。

最后,每个线程都有自己的虚拟机栈,-Xss就是限定了每个线程的虚拟机栈内存的大小。

JDK1.8以后,方法区变成了“元数据区”,-XX:PermSize和-XX:MaxPermSize这两个参数,也相应的变成了-XX:MetaspaceSize和-XX:MaxMetaspaceSize。

3.2 新生代配置

我们来看一个线上示例,更好的理解下如何合理的设置JVM参数。下图是一个电商系统的大致流程,我们重点分析用户提交支付订单-支付系统这一块:

对于支付系统,其系统压力来自很多方面,包括高并发访问、大量日订单数据存储、高可用保障等等,抛开这些系统架构层面的东西,我们只看JVM层面,系统最大的压力来自于 频繁的创建和销毁支付订单 ,所以就需要根据以下情况来估计每台机器的JVM参数设置:

  • 支付联机系统的部署机器数量
  • 每台机器的内存
  • 每台机器的JVM堆内存大小

并发量估算

首先,我们先从系统日交易量入手,估算下每秒平均交易量。以笔者曾经做过的某银行核心支付平台为例,除去双11之类促活日外,系统日平均交易量为6000万笔,根据“二八定律”,80%的交易发生在20%的时间内,再乘以经验因子3,峰值每秒交易量大约为9000笔:
$$
TPS = 60000000 80\% / (2460600.2) * 3≈ 9000
$$
假设我们部署20台机器,每台机器每秒大概处理450笔订单:

耗时估算

我们再来估算下一次支付请求处理的耗时,包括订单创建、缓存查询、填充数据、入库等等操作,这里咱们估算的大一点,假设需要1s时间。此时,我们脑子里应该有个流动的模型:

每台机器1秒钟接收到450笔支付订单,然后再JVM新生代创建了450个订单对象,接着1秒钟后,这450笔订单处理完毕,其引用就被回收了,那这些对象在JVM的新生代里就是没人引用的垃圾对象了。

内存估算

我们再来估算下每笔订单所需的内存空间,一般直接根据订单实体Bean中的字段和类型来估算就可以了,比如Integer类型占4字节、Long占8字节等等。根据经验,一个订单对象20个字段差不多了,所以算1个订单对象总共500字节,那450笔订单总共算250kb好了。

完整分析

现在我们已经把整个系统运行的关键环节的数据都分析清楚了,当系统运行时,新生代中积累的无引用对象会越来越多,直到某一刻可能达到了几百兆,把新生代空间都快占满了,然后就会触发“Minor GC”,把新生代里的垃圾对象都回收掉。

真实的线上支付系统,肯定每秒钟还会创建其它各种各样的对象,比如商户数据、信贷数据等等,我们一般把订单模型扩充10倍,来作为整体对象所占的内存大小,250kb*10大概也就3MB,也就是说:

每秒钟创建出来的被Java虚拟机栈的局部变量引用的对象,大致占据的内存空间为3MB左右

结合上述分析,其实我们就可以知道JVM的参数该如何设置了。

一般来说,像支付这种核心业务系统,给的机器配置都是比较好的,笔者之前所在公司的支付系统机器配置最低为 8核16G ,除去机器本身的内存消耗外,给JVM进程12G的内存:

-Xms和-Xmx设置为10G,即整个Java堆内存一共10G;-Xmn设置为8G,即给新生代分配8G 。以每秒创建3MB新生代对象算,新生代占满大约需要45分钟,也就是45分钟左右触发一次“Monir GC”,是可以接受的。

假设业务量更大,从纵向考虑,可以用更高配的机器;从横向考虑,可以横向扩展部署更多应用;从系统架构考虑,可以引入MQ削峰、分库分表或限流,总之只要每台机器处理的请求更少,对JVM的压力就越低。

四、总结

本章我们介绍了JVM的分代垃圾回收机制,并通过一个线上示例讲解了如何对JVM的各个核心参数进行预估配置。关于新生代、老年代、永久代还有许多细节我们没有在本章讲解,后续章节,我们会逐步深入。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/307980.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

复合机器人作为一种新型的智能制造装备高效、精准和灵活的生产方式

随着汽车制造业的快速发展,对于高效、精准和灵活的生产方式需求日益增强。复合机器人作为一种新型的智能制造装备,以其独特的优势在汽车制造中发挥着越来越重要的作用。因此,富唯智能顺应时代的发展趋势,研发出了ICR系列的复合机器…

计算机毕业设计 | SpringBoot航空订票 机票预定购买系统(附源码)

1, 概述 1.1 选题目的 目前,国内航空公司的数量和规模都在扩大,国外航空公司也纷纷着陆中国,这些航空公司之间的竞争可谓日益激烈。配备一个安全、高效、灵活、可靠的客户服务中心系统对于航空公司加强客户服务质量,…

使用Android Compose实现网格列表滑到底部的提示信息展示

文章目录 概述1 效果对比1.1 使用添加Item的办法:1.2 使用自定义的方法 2. 效果实现2.1 列表为空时的提示页面实现2.2 添加Item的方式代码实现2.3 使用自定义的方式实现 3. UI工具类 概述 目前大多数的APP都会使用列表的方式来呈现内容,例如淘宝&#x…

解决Echarts y轴文本超出容器问题

解决Echarts y轴文本超出容器问题 一开始好好的 数据变多之后就被挤出去了 解决方法: // echarts的grid属性 主要就是containLabel这个属性的配置 不设置的话他默认是false, 主要是包含是否包含刻度标签grid: {left: "5%",right: "10%",botto…

linux 里面在docker 里面安装pg 数据库(亲测有效)

目录 1 上传 1 上传 上传之后tar 包,将他变成镜像 输入docker images,发现目前是没有镜像的,现在将tar 包变成镜像 docker load -i postgresql.tar以上就将tar 包变成镜像了 现在在宿主机找一个地方,存放数据库的数据 /home/softinstall/…

全网独家:基于openEuler-20.03-LTS-SP4底包构建opengaussV5.0.1LTS的单机极简版数据库容器

本文尝试基于openEuler-20.03-LTS-SP4底包构建opengaussV5.0.1LTS的单机版极简版数据库容器。 一、软件包源 1、openEuler-20.03-LTS容器底包 openEuler-20.03-LTS-SP4 下载链接 sha256:24d8f51c1f3a79eb975c4e498cadd9055bfd708d66c15935ec46664d0f975a7b openEuler-dock…

@DependsOn:解析 Spring 中的依赖关系之艺术

欢迎来到我的博客,代码的世界里,每一行都是一个故事 DependsOn:解析 Spring 中的依赖关系之艺术 前言简介基础用法高级用法在 XML 配置中使用 DependsOn通过 Java Config 配置实现依赖管理 生命周期与初始化顺序Bean 生命周期的关键阶段&…

高照数量关系(一)—— 倍数特性、方程问题、周期问题

倍数特性 整除型 (1)口诀法:(常用于3、4、5、9)3/9看各个位数字之和,5看末位,4看末两位。 3/9 -> 看各位数字之和能否被3/9整除,例:124345 2/5 ->看数字末一位能…

【Linux】进程

----------------| 本文目录 |---------------- 1. 进程1.1 基本概念1.2 描述进程 - PCB1.2.1 task_struct - PCB的一种1.2.2 task_struct 内容分类 1.3 组织进程1.4 查看进程1.5 通过系统调用获取进程标示符1.6 通过系统调用创建进程 - fork初识 2. 进程状态2.1 看看Linux内核…

美创科技第59号安全实验室最新力作!《内网渗透实战攻略》出版发行

总结先进攻防实战经验,基于创新入侵生命周期模型,为提升渗透实战能力提供系统操作教程!近期,美创科技创始人&CEO柳遵梁,美创第59号安全实验室(王月兵、覃锦端、毛菲、刘聪等)撰写的新书《内…

时空序列问题的本质和底层逻辑

本质:Still need to polish this. 底层逻辑:Still need to polish this.See you pretty soon. Reference 【时空序列预测】什么是时空序列问题?这类问题主要应用了哪些模型?主要应用在哪些领域?_mb62b92582e5a0a的技…

办公场景日益多样化 企业如何保持安全?

当前,企业的办公场景日益多样化。远程办公、移动办公、云办公、分支机构等,这些新的办公场景也带来了新的网络安全挑战。以下将介绍一些办公场景带来的安全威胁。 1、远程办公:员工可以在任何地方工作,但同时也带来了网络安全的隐…

支付宝电脑端支付代码

在学习某些项目需要用到支付功能,如支付宝支付。 详细配置 演示沙箱环境下支付,沙箱环境和正式支付只不过一些参数不同 像AppId PrivateKey AlipayPublicKey gatewayUrl 这些参数会有不同。 代码配置 @Component @Data public class payConfig {private String PrivateKey…

springboot配置多数据源

在开发过程中&#xff0c;为了满足需求&#xff0c;会从第三方获取需要的数据&#xff0c;这个时候&#xff0c;除了使用原始的jdbc方式读取数据外&#xff0c;还可以配置多数据源来获取我们想要的数据。 第一步&#xff1a;pom.xml添加依赖 <dependency><groupId>…

JPackage指令将可执行Jar包打包成EXE运行程序

jpackage是jdk14正式加入的一个用于独立打包的工具。 官网简介翻译&#xff1a; jpackage工具将以Java应用程序和Java运行时映像作为输入&#xff0c;并生成一个包含所有必要依赖项的Java应用程序映像。它可以生成特定于平台格式的本机软件包&#xff0c;例如Windows上的exe或…

KVM系统虚拟化性能测试过程总结

buildroot编译 为啥要用buildroot 支持很多&#xff1a;交叉编译工具链、根文件系统生成、内核映像编译和引导加载程序编译。使用简单&#xff1a;使用类似内核的menuconfig、gconfig和xconfig配置界面&#xff0c;使用buildroot构建基本系统很容易。支持很多的包&#xff1a…

1.10 Unity中的数据存储 XML

一、XML 1.介绍 XML是一个文档后缀名是*.xmlXML是一个特殊格式的文档XML是可扩展的标记性语言XML是Extentsible Markup Language的缩 写XML是由万维网联盟(W3C)创建的标记语言&#xff0c;用于定义编码人类和机器可以读取的文档的语法。它通过使用定义文档结构的标签以及如何…

基于ubuntu2204使用kubeadm部署k8s集群

部署k8s集群 基础环境配置安装container安装runc安装CNI插件部署1.24版本k8s集群&#xff08;flannel&#xff09;安装crictl使用kubeadm部署集群节点加入集群部署flannel网络配置dashboard 本集群基于ubuntu2204系统使用kubeadm工具部署1.24版本k8s&#xff0c;容器运行时使用…

AIGC视频生成:Pika1.0快速入门详解

Pika1.0快速入门详解 一、简介二、登录三、参数设置1、改变画面大小&#xff08;Aspect ratio&#xff09;2、改变帧数大小&#xff08;Frames per second&#xff09;3、镜头平移&#xff08;Camera control&#xff09;4、画面运动控制&#xff08;Strength of motion&#x…

[Linux进程(一)] 什么是进程?PCB的底层是什么?以及进程标识符pid与ppid

文章目录 1、前言2、描述进程 — PCB(os怎么管理进程呢)3、查看进程3.1 方法一3.2 方法二 4、系统调用获取进程标示符(PID)4.1 获取进程的ID4.2 获取进程的父进程ID 5、系统调用创建子进程-fork 1、前言 大家经常都在讲进程&#xff0c;而它到底是什么呢&#xff1f; 这里给大…