Skip to content
Xiaoyu's Blog
Go back

JAVA基础(七、JVM运行内存-堆 )

一、概念

1.1 简介

对于Java应用程序来说, Java堆(Java Heap) 是虚拟机所管理的内存中最大的一块。 Java堆是被所 有线程共享的一块内存区域, 在虚拟机启动时创建。 此内存区域的唯一目的就是存放对象实例, Java 世界里“几乎”所有的对象实例都在这里分配内存。“几乎”是指从实现角度来看, 随着Java语 言的发展, 现在已经能看到些许迹象表明日后可能出现值类型的支持, 即使只考虑现在, 由于即时编译技术的进步, 尤其是逃逸分析技术的日渐强大, 栈上分配、 标量替换优化手段已经导致一些微妙的变化悄然发生, 所以说Java对象实例都分配在堆上也渐渐变得不是那么绝对了。

Java 堆从 GC 的角度还可以细分为: 新生代(Eden 区、 From Survivor 区和 To Survivor 区)和老年
代。

1.2 特点

  1. 是Java虚拟机所管理的内存中最大的一块。
  2. 堆是jvm所有线程共享的。

    堆中也包含私有的线程缓冲区 Thread Local Allocation Buffer (TLAB)

  3. 在虚拟机启动的时候创建。
  4. 唯一目的就是存放对象实例,几乎所有的对象实例以及数组都要在这里分配内存。
  5. Java堆是垃圾收集器管理的主要区域。
  6. 因此很多时候java堆也被称为“GC堆”(Garbage Collected Heap)。从内存回收的角度来看,由于现在收集器,基本都采用分代收集算法,所以Java堆还可以细分为:新生代和老年代;新生代又可以分为:Eden 空间、From Survivor空间、To Survivor空间。
  7. java堆是计算机物理存储上不连续的、逻辑上是连续的,也是大小可调节的(通过-Xms和-Xmx控制)。
  8. 方法结束后,堆中对象不会马上移出仅仅在垃圾回收的时候时候才移除。
  9. 如果在堆中没有内存完成实例的分配,并且堆也无法再扩展时,将会抛出OutOfMemoryError异常。

二、 设置堆空间大小

Xmx20m -Xms5m -XX:+PrintGCDetails

测试:

package com.study.jvm.heap;

public class HeapDemo {

    public static void main(String[] args) {
        byte[] bytes = new byte[1024 * 1024 * 13];
        System.out.println("MaxMemory:" +  Runtime.getRuntime().maxMemory() / 1024 /1024 + "m");
        System.out.println("FreeMemory:" +  Runtime.getRuntime().freeMemory() / 1024 /1024 + "m");
        System.out.println("TotalMemory:" +  Runtime.getRuntime().totalMemory() / 1024 /1024 + "m");
    }
}

运行结果:

MaxMemory:18m
FreeMemory:2m
TotalMemory:5m

大家可以发现,这里打印出来的Xmx值和设置的值之间是有差异的,total Memory和最大的内存之间还是存在一定差异的,就是说JVM一般会尽量保持内存在一个尽可能低的层面,而非贪婪做法按照最大的内存来进行分配。

byte[] bytes = new byte[1024 * 1024 * 13];

在申请分配了13m内存空间之后,total memory上升了,同时可用的内存也上升了,可以发现其实JVM在分配内存过程中是动态的, 按需来分配的。

三、堆的分类

现在垃圾回收器都使用分代理论,堆空间也分类如下:
在
Java7 Hotspot虚拟机中将Java堆内存分为3个部分:

在Java8以后,由于方法区的内存不在分配在Java堆上,而是存储于本地内存元空间Metaspace中,所以永久代就不存在了,在几天前(2018年9约25日)Java11正式发布以后,我从官网上找到了关于Java11中垃圾收集器的官方文档,文档中没有提到“永久代”,而只有青年代和老年代。

3.1 老年代和年轻代

3.1.1 JVM中存储java对象可以被分为两类:

  1. 年轻代(Young Gen):年轻代主要存放新创建的对象,内存大小相对会比较小,垃圾回收会比较频繁。年轻代分成1个Eden Space和2个Suvivor Space(from 和to)。
  2. 年老代(Tenured Gen):年老代主要存放JVM认为生命周期比较长的对象(经过几次的Young Gen的垃圾回收后仍然存在),内存大小相对会比较大,垃圾回收也相对没有那么频繁。

3.1.2 配置新生代和老年代堆结构占比

从图中可以看出: 堆大小 = 新生代 + 老年代。其中,堆的大小可以通过参数 –Xms、-Xmx 来指定。

默认的,新生代 ( Young ) 与老年代 ( Old ) 的比例的值为 1:2 ( 该值可以通过参数 –XX:NewRatio 来指定 ),即:新生代 ( Young ) = 1/3 的堆空间大小。老年代 ( Old ) = 2/3 的堆空间大小。其中,新生代 ( 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.1.3 对象分配过程

JVM设计者不仅需要考虑到内存如何分配,在哪里分配等问题,并且由于内存分配算法与内存回收算法密切相关。
因此还需要考虑GC执行完内存回收后是否存在空间中间产生内存碎片。

分配过程

-XX:MaxTenuringThreshold=N

分配流程图解:

四、堆GC

Java 中的堆也是 GC 收集垃圾的主要区域。GC 分为两种:一种是部分收集器(Partial GC)另一类是整堆收集器(Fu'll GC)

部分收集器: 不是完整收集java堆的的收集器,它又分为:

整堆收集(Full GC):收集整个java堆和方法区的垃圾收集器

4.1 年轻代GC触发条件:

4.2 老年代GC (Major GC)触发机制:

4.3 FullGC 触发机制:


Share this post:

Previous Post
JAVA基础(八、JVM线程和内存区域 )
Next Post
gRPC实战(七、负载均衡(二))