一文了解Hotspot虚拟机下JAVA对象从创建到回收的生命周期

news2024/9/22 9:36:59

Java虚拟机是Java的核心和基础,他是Java编译器和操作系统平台之间处理器,能实现跨平台运行Java程序。本文主要讲解的是虚拟机如何管理对象,即Java对象在JVM虚拟机中被创建到回收的流程

Java对象从创建到回收的生命周期

  • 对象创建流程
    • 1.类加载检查
      • 类加载过程
    • 2.分配内存
      • JVM运行时内存数据区布局
      • 运行时数据区关系
    • 3.初始化
    • 4.设置对象头
    • 5.执行init方法
  • 对象回收流程
    • 1.标记存活对象
    • 2.非存活对象回收
      • 垃圾回收策略
      • 垃圾回收算法
      • 垃圾回收器
        • Serial收集器
        • Parallel Scavenge收集器
        • ParNew收集器
        • CMS

对象创建流程

public class Main {
    public static void main(String[] args) {
           createObject();
    }
    public static void createObject() {
        Object object = new Object();
    }
}

当虚拟机碰到new或者克隆一个类的实例对象时【如上new Object()】,它会经历如下过程:
在这里插入图片描述

1.类加载检查

当虚拟机遇到一个new指令的时候,首先去判断这个指令的参数是否能在方法区找到类的引用,并且检查这个引用是否已经被加载,解析和初始化过。如果没有,则必须先执行先执行类的加载过程

类加载过程

参考博客:Java类的加载过程

2.分配内存

当类加载完以后,虚拟机将会为新生对象分配内存。对象所需的内存大小在类加载完后便可完全确定,便会将Java堆中一块确定大小的内存从Java堆中划分出来
此时会涉及两个问题
2.1.虚拟机如何分配内存
2.2.在并发情况下,如何保证多个对象的内存不重叠

JVM运行时内存数据区布局

在这里插入图片描述
a)堆区
Java堆是用来存储对象本身的以及数组。堆是被所有线程共享的,在JVM中只有一个堆。堆有分代逻辑,堆划分为了年轻代,老年代;年轻代分为了eden区,s0区,s1区,比例默认为老年代:年轻代=2:1,eden:s0:s1=8:1:1;
这里引发两个问题:
1.为什么堆中要分代?
2.为什么年轻代还要分为3个区?为什么区的比例为8:1:1?

会涉及到关于对象回收的机制:
1.堆中分代是为了提高效率,对象是有生命周期,有的对象存活时间长,有的对象存活时间短,存活时间长的放到老年代;存活时间短的放到年轻代。老年代回收频率低点,年轻代回收频率高点
2.分为3个区是为了gc之后方便把存活的对象复制到s1或者s2中,方便内存整理。比如为8:1:1是因为大多数的对象都是朝生夕死的,存活时间比较短,所以jvm默认8:1:1的比例是合适的,让 Eden区尽量大,survivor区足够用即可

在这里插入图片描述
b)栈
Java栈中存放一个个栈帧,每个栈帧对应一个被调用的方法,在栈帧中包括:局部变量表,操作栈,动态链接【指向当前方法所属的类的运行时常量池的方法】,方法出口【方法执行完后需返回地址】
在这里插入图片描述
c)本地方法栈
本地方法栈与Java栈作用和原理类似,区别在于Java栈是为执行本地方法服务的,而本地方法栈则是执行本地方法服务的
d)方法区
与堆一样,是线程共享的区域。在方法区中存储了类信息,静态变量,常量以及编译器编译后的代码
e)程序计数器
在jvm中,多线程是通过线程轮流切换来获得cpu执行时间的。为了保证每个线程在切换后能够恢复到切换之前程序执行的位置,每个线程都有自己独立的程序计数器

运行时数据区关系

由下图可以看出:堆和方法区是所有线程共用的,本地方法栈,Java栈,程序计数器是线程私有的
在这里插入图片描述
看到堆中存放对象,引发两个问题:
1.所有新创建对象都存放到堆中吗?
2.堆中既有老年代又有年轻代,新创建的对象应该放在哪里

我们先整体看一下对象在内存的分配流程,然后再逐一回答上面的问题
在这里插入图片描述

根据图流程所示回答第一个不是所有的对象都会存放到堆中,有可能是栈。栈上分配主要是虚拟机根据该对象是否被外部访问。如果不会逃逸出该对象在栈上分配,随着方法的结束出栈而销毁。
对象逃逸分析:分析对象的动态作用域,当一个对象在方法中被定义后,它可能被外部方法引用,例如被作为参数传递到其他地方去

public User createUser(){
	User user = new User();
	user.setName("judy");
	user.setAge(18);
	return user;
}

public void  test1(){
	User user = createUser();
}

像上面的方法,它可能被其他方法调用,无法在栈内分配;而像作用域非常确定,比如:

public void createUser(){
	User user = new User();
	user.setName("judy");
	user.setAge(18);
}

那这种作用域非常确定,不会被其他方法调用,就可以在栈内分配。
因为栈内存比较小,为了防止栈内没有一大块连续空间导致对象内存不够分配,虚拟机会采用标量替换方法去解决这个问题
标量替换:当对象确定在栈内分配时候,并且开启了标量替换,它不会直接创建对象,因为创建对象需要设置对象头也会占用空间,而是将该Java对象成员变量进行分解,分为若干个被这个方法使用的局部成员变量,然后为分解后的变量分配空间。jdk7以后默认开启。标量一般是指Java中的基本数据类型

解决完第一个问题:对象否则都存在于堆中?我们再来看第二个问题:堆中既有老年代又有年轻代,新创建的对象应该放在哪里?
第二个问题的答案是:一个新创建的对象,如果非常大,超过虚拟机设置的值,就会直接放到老年代;但是如果没有超过限定值,先放到Eden中。至于S0,S1区,对象在通过minorGC以后才会存放到这两个区域中,后续讲解到对象的垃圾回收时,还会重点解决一下。

3.初始化

内存分配完成后,虚拟机需要将分配到的内存空间都初始化为零值【不包括对象头】。这一步保证对象的实例字段在Java代码中可以不赋初始值就直接使用,程序就能访问到这些字段的数据类型所对应的零值

4.设置对象头

对对象的实例属性值设置为零值后,需要对对象进行必要的设置,例如这个对象属于哪个类的实例,如果才能找到类的元数据信息,对象的哈希码,对象gc分代信息。这些信息在对象的对象头中。
对象分为三部分信息:对象头,实例数据,对象填充
4.1对象头:分为三部分信息
4.1.1第一部分存储对象自身运行时的数据
4.1.2第二部分存储类型指针,即对象指向它的类元数据的指针,虚拟机通过这个指针确定这个对象属于哪个类的实
4.1.3第三部分是数组长度,若为数组则有数,若没有则为空
在这里插入图片描述
4.2实例数据:对象的实例属性
4.3对象填充:当对象头+实例数据的字节数不是8字节的整数时,字节填充使其为8字节的整数倍,加快内存寻址

5.执行init方法

为对象赋值和执行构造方法

对象回收流程

1.标记存活对象

对象回收之前,先判断对象是否活着,判断对象是否活着两种方式
1.引用计数法
给对象中添加一个引用计数器,每当有一个地方引用它,计数器就加1;
当引用失效,计数器就减1;任何时候计数器为0的对象就是不可能再被使用的。这个方法实现简单,效率高,但是目前主流的虚拟机中并没有选择这个算法来管理内存,其最主要的原因是它很难解决对象之间相互循环引用的问题。
2.可达性分析算法
将"GC Roots"对象作为起点,从这些起点开始向下搜索引用的对象,找到的对象是非垃圾的对象,其余标记为垃圾对象。
GC Roots根节点:线程栈的本地变量,静态变量,本地方法栈中的变量
其中引用关系可以分为:强引用,软引用,弱引用,虚引用
强引用:普通的变量引用

public static User user=new User();

软引用:被SoftReference对象包裹着,正常情况不会被回收,但是gc做完后发现释放不出空间存放新的对象,则会将这些软引用对象回收掉。用来实现内存敏感的高速缓存

public static SoftReference<User> user=new SoftReference<User>(new User());

弱引用:将对象用WeakReference软引用类型的对象包裹着,弱引用跟没有引用差不多,GC会直接回收掉。
虚引用:最弱的引用,几乎不用

引发一个问题,是否没有可达性分析分析到的对象就会被回收?
答案是不会,一个对象被回收要经历两次标记的过程
1.第一次标记进行筛选
筛选的条件是此对象是否有必要执行finalize()方法。当对象没有覆盖finalize方法,对象将直接被回收。
2.第二次标记
第二次标记如果这个对象覆盖了finalize方法,finalize方法是对象脱逃死亡命运的最后一次机会,如果对象要在finalize()中成功拯救
自己,只要重新与引用链上的任何的一个对象建立关联即可,譬如把自己赋值给某个类变量或对象的成员变量,
那在第二次标记时它将移除出“即将回收”的集合。
如果对象这时候还没逃脱,那基本上它就真的被回收了。
注意:
一个对象的finalize()方法只会被执行一次,
也就是说通过调用finalize方法自我救命的机会就一次。

2.非存活对象回收

被标记的对象是如何在内存中被回收,首先堆在内存是分代的,两者根据对象生命周期的不同,存放着不同类型的对象。那意味着也需要采取不同的回收策略【年轻代回收和老年代回收】,回收策略也有着相应的回收算法,根据不同垃圾回收算法JVM也提供了一系列垃圾回收器

垃圾回收策略

Minor GC/Yong GC:指发生新生代的垃圾收集动作,Minor GC会非常的频繁,回收速度一般比较快;
Major GC/Full GC:一般会回收老年代,年轻代,方法区的垃圾,Major GC的速度一般会比Minor GC慢
在这里插入图片描述

垃圾回收算法

  1. 标记复制
    将内存分为大小相同的两块,每次使用其中的一块。当一块用完后,将存活的对象复制到另一块去,然后把使用的空间一次清理掉。
    在这里插入图片描述

  2. 标记清除
    算法分为标记和清除阶段:标记存活的对象,统一回收所有未被标记的对象;也可以标记需要回收的对象,在标记完成后统一回收被标记的对象。它是最基础的手机算法,比较简单,但是会带来1个问题:清除后产生大量不连续的碎片
    在这里插入图片描述

  3. 标记整理
    根据老年代特有的标记算法,标记过程和标记-清除算法一样,但后续步骤不是直接对可回收的对象回收,而是让所有存活的对象向一端移动,然后直接清理掉边界以外的内存。
    在这里插入图片描述

垃圾回收器

垃圾回收算法是内存分配的方法论,那么垃圾回收器是内存分配的具体实现
在这里插入图片描述

Serial收集器

Serial(串行)收集器是最基本、历史最悠久的垃圾收集器了。
大家看名字就知道这个收集器是一个单线程收集器了。它的“单线程”的意义不仅仅意味着它只会使用一条垃圾收集线程去完成垃圾收集工作,更重要的是它在进行垃圾收集工作的时候必须暂停其他所有的工作线程(“StopTheWorld”),直到它收集结束。新生代采用复制算法,老年代采用标记-整理算法。
Serial Old收集器是Serial收集器的老年代版本,它同样是一个单线程收集器。可以和CMS收集器搭配使用
在这里插入图片描述
配置参数如下:
-XX:+UseSerialGC
-XX:+UseSerialOldGC

Parallel Scavenge收集器

Parallel收集器其实就是Serial收集器的多线程版本,除了使用多线程进行垃圾收集外,
其余行为(控制参数、收集算法、回收策略等等)和Serial收集器类似。
默认的收集线程数跟cpu核数相同,当然也可以用参数(-XX:ParallelGCThreads)指定收集线程数,但是一般不推荐修改。Parallel Scavenge收集器关注点是吞吐量(高效率的利用CPU)
CMS等垃圾收集器的关注点更多的是用户线程的停顿时间(提高用户体验)
新生代采用复制算法,老年代采用标记-整理算法。
Parallel Old收集器是Parallel Scavenge收集器的老年代版本。
使用多线程和“标记-整理”算法。在注重吞吐量以及CPU资源的场合,都可以优先考虑
ParallelScavenge收集器和Parallel Old收集器(JDK8默认的新生代和老年代收集器)。
在这里插入图片描述

参数配置:
-XX:+UseParallelGC(年轻代),-XX:+UseParallelOldGC(老年代)

ParNew收集器

ParNew收集器其实跟Parallel收集器很类似,区别主要在于它可以和CMS收集器配合使用。
新生代采用复制算法,老年代采用标记-整理算法。
在这里插入图片描述

参数配置:
-XX:+UseParNewGC

CMS

CMS(Concurrent Mark Sweep)收集器是一种以获取最短回收停顿时间为目标的收集器。
它非常符合在注重用户体验的应用上使用,它是HotSpot虚拟机第一款真正意义上的并发收集器,它第一次实现了让垃圾收集线程与用户线程(基本上)同时工作。从名字中的MarkSweep这两个词可以看出,CMS收集器是一种“标记-清除”算法实现的,它的运作过程相比于前面
几种垃圾收集器来说更加复杂一些。
整个过程分为四个步骤:
初始标记:
暂停所有的其他线程(STW),并记录下gcroots直接能引用的对象,速度很快。
并发标记:
并发标记阶段就是从GCRoots的直接关联对象开始遍历整个对象图的过程,
这个过程耗时较长但是不需要停顿用户线程,可以与垃圾收集线程一起并发运行。
因为用户程序继续运行,可能会有导致已经标记过的对象状态发生改变。
重新标记:
重新标记阶段就是为了修正并发标记期间因为用户程序继续运行而导致标记产生变动的那一部分对象的标记记录,这个阶段的停顿时间一般会比初始标记阶段的时间稍长,远远比并发标记阶段时间短。主要用到三色标记里的增量更新算法做重新标记。
并发清理:开启用户线程,同时GC线程开始对未标记的区域做清扫。这个阶段如果有新增对象会被标记为黑色不做任何处理
并发重置:重置本次GC过程中的标记数据。
在这里插入图片描述
参数配置:
-XX:+UseConcMarkSweepGC(old)

Java虚拟机对对象的管理讲解的这里,有问题可以留言喔

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

MyBatis 的一级、二级缓存机制

目录标题缓存什么是缓存为什么使用缓存什么样的数据能使用缓存&#xff0c;什么样的数据不能使用适用于缓存不适用于缓存MyBatis 一级缓存、二级缓存关系1. 一级缓存1.1 什么是一级缓存mybatis1.2 一级缓存配置1.3 什么情况下会命中一级缓存mybatis清除一级缓存的几种方法1.4 内…

Delphi 10.4.2使用传统代码提示方案(auto complete)(转)

Delphi 10.4重点是实现了LSP&#xff0c;但现在最新的10.4.2还是不成熟&#xff0c;无法满足日常需要&#xff0c;不过没关系&#xff0c;可以设置为原有的方案&#xff0c;如下图&#xff1a;具体操作&#xff1a;Tools->Options->Editor->language->Code Insight…

迷宫问题图解 : 基于骨架提取、四邻域

目录 1. 迷宫的连通域 2. How to remove branch &#xff1f; 3. 基于4邻域的 remove 分支 3.1 找到分支的端点 3.2 4邻域的 remove 分支 3.3 循环移除分支 3.4 code 4. 迷宫路线 4.1 预处理 4.2 提取骨架 4.3 分支的端点 4.4 去除分支的端点 4.5 循环去除分支 4…

Java-合并两个链表

每日一题 Java-合并两个链表 给你两个链表 list1 和 list2 &#xff0c;它们包含的元素分别为 n 个和 m 个。 请你将 list1 中下标从 a 到 b 的全部节点都删除&#xff0c;并将list2 接在被删除节点的位置。 下图中蓝色边和节点展示了操作后的结果&#xff1a; 请你返回结果…

linux下redis安装 及常用命令

安装及常用命令 redis的yum方式安装 先查看是否已经安装redis执行命令 rpm -qa | grep redis如果存在&#xff0c;将存在的卸载&#xff1a;(-y 代表自动选择) yum remove xxx -y在线安装redis yum install redis安装本地已经下载好的redis安装包 yum localinstall redis6.2…

基于Spring、Spring MVC、MyBatis的招聘管理系统

文章目录项目介绍主要功能截图&#xff1a;首页账户管理招聘建议部分代码展示设计总结项目获取方式&#x1f345; 作者主页&#xff1a;Java韩立 &#x1f345; 简介&#xff1a;Java领域优质创作者&#x1f3c6;、 简历模板、学习资料、面试题库【关注我&#xff0c;都给你】 …

流程引擎之Camunda简介

背景Camunda 是支持 BPMN&#xff08;工作流和流程自动化&#xff09;、CMMN&#xff08;案例管理&#xff09; 和 DMN&#xff08;业务决策管理&#xff09; java 框架。Camunda 基于Activiti5 保留了 PVM&#xff0c;其开发团队也是从 activiti 中分裂出来的。Camunda 来自拉…

KubeSphere实战

文章目录一、KubeSphere平台安装1、Kubernetes上安装KubeSphere1.1 安装docker1.2 安装Kubernetes1.3 前置环境之nfs存储1.4 前置环境之metrics-server1.5 安装KubeSphere2、Linux单节点部署KubeSphere3、Linux多节点部署KubeSphere(推荐)二、KubeSphere实战1、多租户实战2、中…

Spring中的数据校验--进阶

分组校验 场景描述 在实际开发中经常会遇到这种情况&#xff1a;添加用户时&#xff0c;id是由后端生成的&#xff0c;不需要校验id是否为空&#xff0c;但是修改用户时就需要校验id是否为空。如果在接收参数的User实体类的id属性上添加NotNull&#xff0c;显然无法实现。这时…

【飞桨AI-Python小白逆袭大神课程】作业3-《青春有你2》选手数据分析

目录 一、数据准备 1、文件数据以json文件格式保存&#xff1a; 二、数据分析 2、数据分析四剑客&#xff1a; &#xff08;1&#xff09;Numpy &#xff08;2&#xff09;pandas &#xff08;3&#xff09;Matplotlib &#xff08;4&#xff09;PIL &#xff08;5&#x…

操作系统题目收录(十一)

1、操作系统采用分页存储管理方式&#xff0c;要求&#xff08;&#xff09;。 A&#xff1a;每个进程拥有一张页表&#xff0c;且进程的页表驻留在内存中B&#xff1a;每个进程拥有一张页表&#xff0c;但只有执行进程的页表驻留在内存中C&#xff1a;所有进程共享一张页表&a…

django项目实战(django+bootstrap实现增删改查)

目录 一、创建django项目 二、修改默认配置 三、配置数据库连接 四、创建表结构 五、在app当中创建静态文件 六、页面实战-部门管理 1、实现一个部门列表页面 2、实现新增部门页面 3、实现删除部门 4、实现部门编辑功能 七、模版的继承 1、创建模板layout.html 1&…

Django框架之模型视图--Session

Session 1 启用Session Django项目默认启用Session。 可以在settings.py文件中查看&#xff0c;如图所示 如需禁用session&#xff0c;将上图中的session中间件注释掉即可。 2 存储方式 在settings.py文件中&#xff0c;可以设置session数据的存储方式&#xff0c;可以保存…

基于springboot的网上图书商城的设计与实现(程序+详细设计文档)

大家好✌&#xff01;我是CZ淡陌。在这里为大家分享优质的实战项目&#xff0c;本人在Java毕业设计领域有多年的经验&#xff0c;陆续会更新更多优质的Java实战项目&#xff0c;希望你能有所收获&#xff0c;少走一些弯路&#xff01; &#x1f345;更多优质项目&#x1f447;&…

Rust学习入门--【17】Rust Slice(切片)类型

系列文章目录 Rust 语言是一种高效、可靠的通用高级语言&#xff0c;效率可以媲美 C / C 。本系列文件记录博主自学Rust的过程。欢迎大家一同学习。 Rust学习入门–【1】引言 Rust学习入门–【2】Rust 开发环境配置 Rust学习入门–【3】Cargo介绍 Rust学习入门–【4】Rust 输…

RocketMQ云服务器和本地基础安装搭建及可视化控制台安装使用

一起学编程&#xff0c;让生活更随和&#xff01; 如果你觉得是个同道中人&#xff0c;欢迎关注博主gzh&#xff1a;【随和的皮蛋桑】。 专注于Java基础、进阶、面试以及计算机基础知识分享&#x1f433;。偶尔认知思考、日常水文&#x1f40c;。 目录一、RocketMQ 介绍1、Ro…

分布式事务--理论基础

1、事务基础 1.1、什么是事务 事务可以看做是一次大的活动&#xff0c;它由不同的小活动组成&#xff0c;这些活动要么全部成功&#xff0c;要么全部失败。 1.2、本地事务 在同一个进程内&#xff0c;控制同一数据源的事务&#xff0c;称为本地事务。例如数据库事务。 在计…

PyTorch 并行训练 DistributedDataParallel完整代码示例

使用大型数据集训练大型深度神经网络 (DNN) 的问题是深度学习领域的主要挑战。 随着 DNN 和数据集规模的增加&#xff0c;训练这些模型的计算和内存需求也会增加。 这使得在计算资源有限的单台机器上训练这些模型变得困难甚至不可能。 使用大型数据集训练大型 DNN 的一些主要挑…

SpringBoot监控

文章目录一、PrometheusGrafana监控Springboot1、简介2、SpringBoot应用镜像搭建2.1 springboot应用创建2.2 镜像创建3、Prometheus3.1 概述3.2 Prometheus创建4、Grafana可视化监控4.1 可视化4.2 告警设置二、轻量级日志系统Loki1、简介1.1 介绍1.2 与ELK差异2、grafana loki日…

linux宝塔安装和部署node全栈项目

使用服务器:阿里云ECS系列 服务器操作系统: Alibaba Cloud Linux 2.1903 LTS 64位 连接服务器方式: Workbench远程连接 使用公网IP登录 Workbench远程桌面&#xff0c;使用命令安装linux宝塔面板操作服务器: 1.登录linux宝塔面板&#xff0c;使用终端命令安装linux宝塔 yum i…