2.2 堆在整个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异常。


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

参考文献:
http://www.importnew.com/14630.html Java 堆内存
http://blog.csdn.net/ylyg050518/article/details/52244994 Java虚拟机(二)——Java堆内存划分

一. 

堆内存划分:

 

  • 堆大小 = 新生代 + 老年代。堆的大小可通过参数–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是如何触发的,又是如何工作的? 如下图:

 

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

 

 

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

我们使用的工具是jvisualvm工具,这是jdk自带的一个工具

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

package com.lxl.jvm;

import java.util.ArrayList;
import java.util.List;

public class HeapTest {

    static void main(String[] args) throws InterruptedException {
        List<User> userList = new ArrayList<>();

        while (true) {
            userList.add(new User());
            Thread.sleep(10);
        }
    }
}

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

userList: 是放在栈中的局部变量表中的一个变量
(): 是放在堆中的一个对象

new User(): 在堆中构建一个新的User对象,并将这个对象添加到new ArrayList()中. 

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

 

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

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

 

 

 

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

 从这个图上,我们可以看到每过一段您时间,触发一次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,这时候程序就发生混乱了. 

 

相关文章

jinfo 命令可以用来查看 Java 进程运行的 JVM 参数,命令如下...
原文链接:https://www.cnblogs.com/niejunlei/p/5987611.ht...
java 语言, 开发者不能直接控制程序运行内存, 对象的创建都是...
jvm
1.jvm的简单抽象模型:  2.类加载机制     双亲委派模...
堆外内存JVM启动时分配的内存,称为堆内存,与之相对的,在代...
1.springboot和tomcat2.springcloud的请求如何通过网关鉴权?...