堆在整个jvm内存中的运行流程以及jvisualvm工具的使用

简介: 堆在整个jvm内存中的运行流程以及jvisualvm工具的使用

一. 堆和GC介绍


java堆的特点


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


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


一.

1187916-20171130145507089-940285672.png


堆内存划分:

 

  • 堆大小 = 新生代 + 老年代。堆的大小可通过参数–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% )的新生代空间。

 

堆的垃圾回收方式


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


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 来设定 ),这些对象就会成为老年代。

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


Full GC


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

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

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

 

---------------------


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

1187916-20200705053409012-1927339835.png


major 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容器里面的对象,

1187916-20200705055228584-125430558.png


二. 使用工具查看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);
        }
    }
}

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

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


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


1187916-20200705062553507-426665726.png

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


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

1187916-20200705062855655-983677875.png

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. 那么为什么一定要stop the world呢? 不Stop the world可不可以呢?


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


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


相关文章
|
11天前
|
存储 算法 Java
聊聊jvm的内存结构, 以及各种结构的作用
【10月更文挑战第27天】JVM(Java虚拟机)的内存结构主要包括程序计数器、Java虚拟机栈、本地方法栈、Java堆、方法区和运行时常量池。各部分协同工作,为Java程序提供高效稳定的内存管理和运行环境,确保程序的正常执行、数据存储和资源利用。
36 10
|
11天前
|
存储 算法 Java
Java虚拟机(JVM)的内存管理与性能优化
本文深入探讨了Java虚拟机(JVM)的内存管理机制,包括堆、栈、方法区等关键区域的功能与作用。通过分析垃圾回收算法和调优策略,旨在帮助开发者理解如何有效提升Java应用的性能。文章采用通俗易懂的语言,结合具体实例,使读者能够轻松掌握复杂的内存管理概念,并应用于实际开发中。
|
11天前
|
Arthas 监控 数据可视化
JVM进阶调优系列(7)JVM调优监控必备命令、工具集合|实用干货
本文介绍了JVM调优监控命令及其应用,包括JDK自带工具如jps、jinfo、jstat、jstack、jmap、jhat等,以及第三方工具如Arthas、GCeasy、MAT、GCViewer等。通过这些工具,可以有效监控和优化JVM性能,解决内存泄漏、线程死锁等问题,提高系统稳定性。文章还提供了详细的命令示例和应用场景,帮助读者更好地理解和使用这些工具。
|
20天前
|
存储 监控 算法
JVM调优深度剖析:内存模型、垃圾收集、工具与实战
【10月更文挑战第9天】在Java开发领域,Java虚拟机(JVM)的性能调优是构建高性能、高并发系统不可或缺的一部分。作为一名资深架构师,深入理解JVM的内存模型、垃圾收集机制、调优工具及其实现原理,对于提升系统的整体性能和稳定性至关重要。本文将深入探讨这些内容,并提供针对单机几十万并发系统的JVM调优策略和Java代码示例。
45 2
|
21天前
|
Arthas 监控 Java
JVM知识体系学习七:了解JVM常用命令行参数、GC日志详解、调优三大方面(JVM规划和预调优、优化JVM环境、JVM运行出现的各种问题)、Arthas
这篇文章全面介绍了JVM的命令行参数、GC日志分析以及性能调优的各个方面,包括监控工具使用和实际案例分析。
37 3
|
19天前
|
存储 Kubernetes 架构师
阿里面试:JVM 锁内存 是怎么变化的? JVM 锁的膨胀过程 ?
尼恩,一位经验丰富的40岁老架构师,通过其读者交流群分享了一系列关于JVM锁的深度解析,包括偏向锁、轻量级锁、自旋锁和重量级锁的概念、内存结构变化及锁膨胀流程。这些内容不仅帮助群内的小伙伴们顺利通过了多家一线互联网企业的面试,还整理成了《尼恩Java面试宝典》等技术资料,助力更多开发者提升技术水平,实现职业逆袭。尼恩强调,掌握这些核心知识点不仅能提高面试成功率,还能在实际工作中更好地应对高并发场景下的性能优化问题。
|
21天前
|
存储 安全 Java
jvm 锁的 膨胀过程?锁内存怎么变化的
【10月更文挑战第3天】在Java虚拟机(JVM)中,`synchronized`关键字用于实现同步,确保多个线程在访问共享资源时的一致性和线程安全。JVM对`synchronized`进行了优化,以适应不同的竞争场景,这种优化主要体现在锁的膨胀过程,即从偏向锁到轻量级锁,再到重量级锁的转变。下面我们将详细介绍这一过程以及锁在内存中的变化。
33 4
|
3月前
|
Java Docker 索引
记录一次索引未建立、继而引发一系列的问题、包含索引创建失败、虚拟机中JVM虚拟机内存满的情况
这篇文章记录了作者在分布式微服务项目中遇到的一系列问题,起因是商品服务检索接口测试失败,原因是Elasticsearch索引未找到。文章详细描述了解决过程中遇到的几个关键问题:分词器的安装、Elasticsearch内存溢出的处理,以及最终成功创建`gulimall_product`索引的步骤。作者还分享了使用Postman测试接口的经历,并强调了问题解决过程中遇到的挑战和所花费的时间。
|
21天前
|
缓存 算法 Java
JVM知识体系学习六:JVM垃圾是什么、GC常用垃圾清除算法、堆内存逻辑分区、栈上分配、对象何时进入老年代、有关老年代新生代的两个问题、常见的垃圾回收器、CMS
这篇文章详细介绍了Java虚拟机(JVM)中的垃圾回收机制,包括垃圾的定义、垃圾回收算法、堆内存的逻辑分区、对象的内存分配和回收过程,以及不同垃圾回收器的工作原理和参数设置。
45 4
JVM知识体系学习六:JVM垃圾是什么、GC常用垃圾清除算法、堆内存逻辑分区、栈上分配、对象何时进入老年代、有关老年代新生代的两个问题、常见的垃圾回收器、CMS
|
24天前
|
存储 缓存 算法
JVM核心知识点整理(内存模型),收藏再看!
JVM核心知识点整理(内存模型),收藏再看!
JVM核心知识点整理(内存模型),收藏再看!