堆和GC

简介: 堆和GC

一. 堆和GC介绍



1.java堆的特点


《深入理解java虚拟机》是怎么描述java堆的


  • Java堆(Java Heap)是java虚拟机所管理的内存中最大的一块
  • java堆被所有线程共享的一块内存区域
  • 虚拟机启动时创建java堆
  • java堆的唯一目的就是存放对象实例。
  • java堆是垃圾收集器管理的主要区域。
  • 从内存回收的角度来看, 由于现在收集器基本都采用分代收集算法, 所以Java堆可以细分为:新生代(Young)和老年代(Old)。 新生代又被划分为三个区域Eden、From Survivor, To Survivor等。无论怎么划分,最终存储的都是实例对象, 进一步划分的目的是为了更好的回收内存, 或者更快的分配内存。
  • java堆的大小是可扩展的, 通过-Xmx和-Xms控制。
  • 如果堆内存不够分配实例对象, 并且堆也无法在扩展时, 将会抛出outOfMemoryError异常。

1187916-20211008190533330-1922149630.png

2.堆内存划分:


  • 堆大小 = 新生代 + 老年代。堆的大小可通过参数–Xms(堆的初始容量)、-Xmx(堆的最大容量) 来指定。
  • 其中,新生代 ( Young ) 被细分为 Eden 和 两个 Survivor 区域,这两个 Survivor 区域分别被命名为 from 和 to,以示区分。默认的,Edem : from : to = 8 : 1 : 1 。(可以通过参数 –XX:SurvivorRatio 来设定 。
  • 即: Eden = 8/10 的新生代空间大小,from = to = 1/10 的新生代空间大小。
  • JVM 每次只会使用 Eden 和其中的一块 Survivor 区域来为存储对象,所以无论什么时候,总是有一块 Survivor 区域是空闲着的。
  • 新生代实际可用的内存空间为 9/10 ( 即90% )的新生代空间。


3.堆的垃圾回收方式


java堆是GC垃圾回收的主要区域。 GC分为两种: Minor GC、Full GC(也叫做Major GC).


1. Minor GC(简称GC)


Minor GC是发生在新生代中的垃圾收集动作, 所采用的是复制算法。

GC一般为堆空间某个区发生了垃圾回收,

新生代(Young)几乎是所有java对象出生的地方。即java对象申请的内存以及存放都是在这个地方。java中的大部分对象通常不会长久的存活, 具有朝生夕死的特点。

当一个对象被判定为“死亡”的时候, GC就有责任来回收掉这部分对象的内存空间。

新生代是收集垃圾的频繁区域。


回收过程如下:


当对象在 Eden ( 包括一个 Survivor 区域,这里假设是 from 区域 ) 出生后,在经过一次 Minor GC 后,如果对象还存活,并且能够被另外一块 Survivor 区域所容纳(上面已经假设为 from 区域,这里应为 to 区域,即 to 区域有足够的内存空间来存储 Eden 和 from 区域中存活的对象 ),则使用复制算法将这些仍然还存活的对象复制到另外一块 Survivor 区域 ( 即 to 区域 ) 中,然后清理所使用过的 Eden 以及 Survivor 区域 ( 即 from 区域 ),并且将这些对象的年龄设置为1,以后对象在 Survivor 区每熬过一次 Minor GC,就将对象的年龄 + 1,当对象的年龄达到某个值时 ( 默认是 15 岁,可以通过参数 -XX:MaxTenuringThreshold 来设定 ),这些对象就会成为老年代。


但这也不是一定的,对于一些较大的对象 ( 即需要分配一块较大的连续内存空间 ) 则是直接进入到老年代。


2.Full GC


Full GC 基本都是整个堆空间及持久代发生了垃圾回收,所采用的是标记-清除算法。

现实的生活中,老年代的人通常会比新生代的人 “早死”。堆内存中的老年代(Old)不同于这个,老年代里面的对象几乎个个都是在 Survivor 区域中熬过来的,它们是不会那么容易就 “死掉” 了的。因此,Full GC 发生的次数不会有 Minor GC 那么频繁,并且做一次 Full GC 要比进行一次 Minor GC 的时间更长,一般是Minor GC的 10倍以上。

另外,标记-清除算法收集垃圾的时候会产生许多的内存碎片 ( 即不连续的内存空间 ),此后需要为较大的对象分配内存空间时,若无法找到足够的连续的内存空间,就会提前触发一次 GC 的收集动作


扩展: Minor GC是如何触发的, 又是如何工作的? 如下图:

1187916-20200705053409012-1927339835.png

Minor GC是由字节码执行引擎触发的. 当我们的程序中需要new一个对象的时候, 就会将这个对象放入到Eden区域, 当Eden区域中的对象越来越多, 直到满了, 这时放不下了, 就会触发字节码执行引擎发起GC操作. 第一次发起的GC, 将会看看哪些对象还活着, 哪些对象已经不用了, 活着的对象放入survivor中的一个区, 不再被引用的对象, 直接被回收了


如何判断对象是否还活着呢?


字节码执行引擎会去找很多gc root.


什么是gc root呢?


GC Root是一个对象, 以这个对象作为启动点,从这些节点开始向下搜索引用的对象, 找到的对象都标记为非垃圾对象, 其余未标记的对象都是垃圾对象.


GC Root根节点有哪些?


线程栈的局部变量, 方法区中的静态变量, 本地方法栈的变量等等。


垃圾收集的原理


在Math中, 我们看栈中main方法的局部变量表中的math变量. 方法区中的user变量. 他们都是GC Root根对象. 他们指向的是一块堆内存空间.


实质是, GC垃圾回收的过程, 就是寻找GC Root的过程. 从栈中找局部变量, 从方法区中找静态变量. 从GC Root出发, 找到所有的引用变量. 这些变量可能会引用其他的变量, 变量还会再引用其他变量. 直到不再引用其他变量为止, 以上这些都是非垃圾对象. 如果一个对象没有被任何对象引用, 那它就是垃圾对象。


垃圾对象最后就被回收, 非垃圾对象进入到Survivor的一个区域里面. 每次进入sruvivor区域,对象的分代年龄都会+1, 分代年龄保存在哪里呢?保存在对象头里面.


程序还在继续运行, 又会产生新的对象放入到Eden区, 当Eden区又被放满了, 就会再次出发GC, 此时会寻找Eden+sruvivor(一个区域)中的GC Root, 将其标记,


没有被引用的对象被回收, 其他被引用的对象会保存到另一个survivor区域. 分代年龄+1

这样运行, 直到分代年龄为15(默认15,可设置)时, 也就是GC发生了15次还活着的对象, 就会被放到老年代.


通常什么样的对象会被放到老年代呢?


静态变量引用的对象, 静态常量. 比如说: 对象池, 缓存对象, spring容器里面的对象,


二. 使用工具查看GC流转的过程


我们使用的工具是jvisualvm工具, 这是jdk自带的一个工具。这个工具通常是在开发环境使用,因为其本身比较耗性能,所以线上一般不用。本地调试可以使用。


先来准备一段代码, 一段很简单的代码, 不停的去产生新的对象

package com.lxl.jvm;
import java.util.ArrayList;
import java.util.List;
public class HeapTest {
    public static void main(String[] args) throws InterruptedException {
        List<User> userList = new ArrayList<>();
        while (true) {
            userList.add(new User());
            Thread.sleep(10);
        }
    }
}

我们来按照上面的逻辑分析代码


  1. userList: 是放在栈中的局部变量表中的一个变量
    new ArrayList<>(): 是放在堆中的一个对象
  2. new User(): 在堆中构建一个新的User对象, 并分配了一个地址,并将这个地址添加到new ArrayList()中.


这里面 userList是根对象, new User()最终会被newArrayList()引用, 而userList又引用new ArrayList(); 所以, 他们都不会是垃圾, 因此都不会被回收.

1187916-20200705062553507-426665726.png

那么死循环不停的构造对象, 添加引用. Eden区迟早会放满, 放满了就会触发GC, 那么GC能把他们回收呢? 回收不了, 因为都在被GC Root直接或间接引用. 最终都会被放入老年代. 然后还在持续构造新的对象,最终会怎么样?最终会内存溢出. 我们来看看可视化效果。

首先, 我们启动程序, 然后在控制台启动jvisualvm

1187916-20200705063009081-10441889.png

我们来看的是HeapTest, 这里面有很多性能指标可以查看. 我们重点看visual GC. 如果没有visual GC 可以参考这篇文章: https://xiaojin21cen.blog.csdn.net/article/details/106612383

1187916-20200705072444220-679157484.png

image.png

从这个图上,我们可以看到每过一段您时间, 触发一次GC, 因为不能被回收, 因此会转移到另一个survivor区域. 经过15次回收, 还没有收走, 那么就进入到old老年区.

老年区的对象越来越多, 当老年代对象满了以后, 会触发full GC, full GC回收的是整个堆以及方法区的内容. 实际上老年代没有能够回收的对象, 这时候在往老年代放, 就会发生OOM


使用这个工具还可以分析我们自己的程序代码的垃圾回收清空


三. Stop The World



在发生GC的时候, 会发生STW, Stop the world.


1. 什么是Stop The World呢 ?


举个例子:在一个电商网站,用户正在下单,这是由于内存满了,触发GC,这时候整个线程就会处于停滞状态。用户的感受就是一直在loading。。。。直到GC完毕,应用线程恢复工作。所以,Stop The World对我们的用户是有一定影响的。JVM调优主要的目的就是减少Full GC的次数和时间。minor GC也会stop the world,但是他的时间很短,所以我们重点调优还是在full gc


2. 那么为什么一定要stop the world呢? 不STW不可以呢?


回答这个问题, 我们可以使用假设法, 假设没有stop the world 会怎么样?

我们知道, 在垃圾回收之前, 要先找到GC Root, 然后标记是否被引用, 最终没有被引用的对象就是我们要回收的垃圾. 那就是没有对象引用他了.通常会回收这块内存空间地址 这个时候, 如果主线程也在运行, 刚好有一个变量存放在这个内存地址了, 而你并行的触发了GC, 这时候程序就发生混乱了.


这是一种情况,另一种是在触发GC的过程中,一部分变量正在被标记,而GC已经开始了,标记完以后,发现了垃圾,结果由于GC已经扫描完这里了,到这这一块垃圾没有被清理掉,要等待下一次垃圾回收来清理。

相关文章
|
缓存 运维 监控
10分钟带你了解 Linux 系统中的 Top 命令
`top`命令是Linux系统中用于实时监控系统资源利用率的工具,展示CPU、内存使用情况及进程状态。启动`top`只需在终端输入`top`。默认按CPU使用率排序,可通过`P`、`M`、`T`键改变排序。使用`k`键可结束进程,`d`键调整刷新率,`q`键退出。输出信息包括系统负载、进程状态、内存使用等。通过进程列表,可以观察到每个进程的CPU和内存占用、用户、运行时间等。了解`top`能帮助测试工程师排查性能问题。
|
2月前
|
运维 监控 数据可视化
2025年数据库性能压测工具对比与重点测试方案选型指南
文章围绕2025年数据库性能压测展开,介绍主流方案有SaaS化平台、自建集群及开源工具链,各有适用场景。阐述核心指标、行业趋势、面临挑战,对比各方案优劣,给出选型建议及最佳实践,还解答常见问题,助力企业做好数据库压测。
|
存储 算法 Java
Java内存管理深度剖析与优化策略####
本文深入探讨了Java虚拟机(JVM)的内存管理机制,重点分析了堆内存的分配策略、垃圾回收算法以及如何通过调优提升应用性能。通过案例驱动的方式,揭示了常见内存泄漏的根源与解决策略,旨在为开发者提供实用的内存管理技巧,确保应用程序既高效又稳定地运行。 ####
|
SQL 关系型数据库 MySQL
MySQL Workbench的安装与配置
MySQL Workbench的安装与配置
|
Ubuntu Android开发
蓝易云 - Ubuntu上导出APP的base.apk方法说明
注意:在实际的生产环境中,你应该使用 `assembleRelease`任务来构建一个用于发布的Release版本的APK,并且你需要提前配置好签名信息。
539 58
|
JavaScript
vue 指定区域可拖拽的限定拖拽区域的div(如仅弹窗标题可拖拽的弹窗)
vue 指定区域可拖拽的限定拖拽区域的div(如仅弹窗标题可拖拽的弹窗)
354 0
|
Linux 编译器 vr&ar
【库函数】Linux下动态库.so和静态库.a的生成和使用
【库函数】Linux下动态库.so和静态库.a的生成和使用
554 1
|
机器学习/深度学习 数据挖掘 Python
Anaconda下载安装与使用
Anaconda下载安装与使用
622 0
|
传感器 机器学习/深度学习 人工智能
盘一盘 | 基于BEV空间的视觉感知算法模型梳理(自下而上&自上而下)(下)
激光雷达传感器可以提供物体准确的深度信息以及结构信息;但激光雷达传感器提供物体信息的距离比较有限,同时其获得的点云数据与相机传感器采集到的图像信息相比更加稀疏;
盘一盘 | 基于BEV空间的视觉感知算法模型梳理(自下而上&自上而下)(下)