Java JVM堆空间的概述

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-IHg7B2dt-1597737846915)(/../../../image/QQ截图20200720165637.png)]

1.设置堆空间初始值和最大值

通过idea 的-VM参数设置,如下图:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Kzip3grp-1597737846919)(/../../../image/QQ截图20200812101420.png)]

2.堆的核心概述

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-sn8cdY2f-1597737846922)(/../../../image/QQ截图20200815140637.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-LrUijyO1-1597737846926)(/../../../image/QQ截图20200815141134.png)]

内存细分

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-eOqX8FHo-1597737846928)(/../../../image/QQ截图20200815142035.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-LlHFC8mg-1597737846930)(/../../../image/QQ截图20200815142437.png)]

3.堆空间大小的设置

  • 详情请看 Java SE8 官方文档 Ctrl+F 搜索 -Xms

  • 通过JVM参数来设置堆空间的初始值

    • -Xms用来设置堆空间(年轻代+老年代)的初始内存大小
    • -X是jvm的运行参数
    • ms是memory start
  • 通过JVM参数来设置堆空间的最大值

    • -Xmx用来设置堆空间(年轻代+老年代)的最大内存大小
  • 堆的初始大小(以字节为单位)。

    • 此值必须是1024的倍数且大于1 MB。在字母后面加上kK表示千字节,mM表示兆字节,gG表示千兆字节。
    • 以下示例说明如何使用各种单位将分配的内存大小设置为6 MB:
      • -Xms6291456
      • -Xms6144k
      • -Xms6m
      • 如果未设置此选项,则初始大小将设置为为老一代和年轻一代分配的大小之和。可以使用-Xmn选项或-XX:NewSize选项设置年轻代的堆的初始大小。
  • 开发中建议将初始堆内存和最大的堆内存设置成相同的值。

  • 打印GC过程的细节

    • JVM参数设置:-XX:+PrintGCDetails

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2FZ4Z6lh-1597737846932)(/../../../image/QQ截图20200815164200.png)]

添加代码测试**

public class HeapSpaceInitial {
    public static void main(String[] args) {

        //返回Java虚拟机中的堆内存总量
        long initialMemory = Runtime.getRuntime().totalMemory() / 1024 / 1024;
        //返回Java虚拟机试图使用的最大堆内存量
        long maxMemory = Runtime.getRuntime().maxMemory() / 1024 / 1024;

        System.out.println("-Xms : " + initialMemory + "M");
        System.out.println("-Xmx : " + maxMemory + "M");

       System.out.println("系统内存大小为:" + initialMemory * 64.0 / 1024 + "G");
        System.out.println("系统内存大小为:" + maxMemory * 4.0 / 1024 + "G");
  
    }
}

打印GC日志(详情信息看后期博客)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-TEbqAlTj-1597737846933)(/../../../image/QQ截图20200815164951.png)]

4.新生代与老年代

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rStHX0z6-1597737846934)(/../../../image/QQ截图20200816170024.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-oORhHFO4-1597737846936)(/../../../image/QQ截图20200816171716.png)]

  • -XX:NewRatio:设置新生代与老年代的比例。

    • 设置新老一代大小之间的比率。默认情况下,此选项设置为2。下面的示例演示如何将年轻/老人比率设置为1:1(这里就是年轻代占堆空间1:2,老年的占1:2)

      -XX:NewRatio =3 //设置老年代占堆空间的3:4
      
  • -XX:SurvivorRatio:设置新生代中Eden区与survivor区的比例

    - [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Ea2h8WjF-1597737846938)(/../../../image/QQ截图20200816173341.png)]

  • -XX: -UseAdaptivesizePolicy :关闭自适应的内存分配策略( 暂时用不到)

  • Xmn:设置新生代的空间的大小。 ( 一般不设置)

5.图解对象分配的过程

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zL1VtaBY-1597737846939)(/../../../image/QQ截图20200816223946.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YdMpQer0-1597737846940)(/../../../image/QQ截图20200816225810.png)]

6.常用调优工具

  1. JDK命令行
  2. Eclipse :Memory Analyzer Tool
  3. Jconsole
  4. Vi sual VM
  5. Jprofiler(暂时使用) 下载及破解
  6. Java Flight Recorder
  7. GCViewerGC Easy

7.Minor GC、Major GC与Full GC

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hIgordWB-1597737846942)(E:\Users\zhoug\Desktop\y2.技术笔记\Java jvm\image\QQ截图20200817151625.png)]

  • 年轻代GC(Minor GC) 触发机制:

    • 当年轻代空间不足时,就会触发Minor GC,这里的年轻代满指的是
      Eden代满,Survivor满不会引发GCI(每次Minor GC会清理年轻
      代的内存。)
    • 因为Java对象大多都具备朝生夕灭的特性,所以MinorGC非常频
      繁,一般回收速度也比较快。这一-定义既清晰又易于理解。
    • Minor GC会引发STW,暂停其它用户的线程,等垃圾回收结束,用户线
      程才恢复运行。
  • 老年代GC (Major GC/Fu11 GC)触发机制:

    • 指发生在老年代的GC, 对象从老年代消失时,我们说“Major GC”或“Fu11 GC”
      发生了。
    • 出现了Major GC, 经常会伴随至少一.次的Minor GC (但非绝对的,在Paral1el
      Scavenge收集器的收集策略里就有直接进行Major GC的策略选择过程)。
      • 也就是在老年代空间不足时,会先尝试触发Minor GC。如果之后空间还不足,
        则触发Major GC
    • Major GC的速度一般会比Minor GC慢10倍以上,STW的时间更长。
    • 如果Major GC后,内存还不足,就报00M了。
    • Major GC的速度- -般会比Minor GC慢10倍以上。
  • Fu11 GC,触发Fu1l GC执行的情况有如下五种:

    1. 调用System. gc()时,系统建议执行Fu1l GC,但是不必然执行
    2. 老年代空间不足
    3. 方法区空间不足
    4. 通过Minor GC后进入老年代的平均大小大于老年代的可用内存
    5. 由Eden区、survivor space0 (From Space)区向survivor space1 (To
      Space)区复制时,对象大小大于To Space可 用内存,则把该对象转存到老年代,且
      老年代的可用内存小于该对象大小;
      说明: full gc是开发或调优中尽量要避免的。这样暂时时间会短一些。

8.堆空间分代思想

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-UplPFcHp-1597737846943)(E:\Users\zhoug\Desktop\y2.技术笔记\Java jvm\image\QQ截图20200817161201.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-DaA0nmLY-1597737846944)(E:\Users\zhoug\Desktop\y2.技术笔记\Java jvm\image\QQ截图20200817161319.png)]

9.内存分配策略

  • 如果对象在Eden(伊甸园区)出生并经过第一次MinorGC 后仍然存活,并且能被Survivor
    容纳的话,将被移动到Survivor空间中,并将对象年龄设为1 。对象在
    Survivor区中每熬过一次MinorGC ,年龄就增加1 岁,当它的年龄增加到- -定
    程度(默认为15岁,其实每个JVM、每个GC都有所不同)时,就会被晋升到老年代
    中。

  • 对象晋升老年代的年龄阈值,可以通过选项-XX: MaxTenuringThreshold来设置。

  • 针对不同年龄段的对象分配原则如下所示:|

    • 优先分 配到Eden

    • 大对象直接分配到老年代

      • 尽量避免程序中出现过多的大对象长期存活的对象分配到老年代
    • 动态对象年龄判断

      • 如果Survivor区中相同年龄的所有对象大小的总和大于Survivor空
        间的一半,年龄大于或等于该年龄的对象可以直接进入老年代,无须等到
        MaxTenur ingThreshold中要求的年龄。
    • 空间分配担保

      • -XX: Handle PromotionFailure

10.对象分配过程: TLAB

  • 为什么有TLAB ( Thread Local Allocation Buffer ) ?

    • 堆区是线程共享区域,任何线程都可以访问到堆区中的共享数据
    • 由于对象实例的创建在JVM中非常频繁,因此在并发环境下从堆区中划分内
      存空间是线程不安全的;
    • 为避免多个线程操作同一地址,需要使用加锁等机制,进而影响分配速度。
  • 什么是TLAB ?

    • 从内存模型而不是垃圾收集的角度,对Eden区域继续进行划分,JVM为
      每个线程分配了一个私有缓存区域,它包含在Eden空间内。
    • 多线程同时分配内存时,使用TLAB可以避免一系列的非线程安全问题,
      同时还能够提升内存分配的吞吐量,因此我们可以将这种内存分配方式称
      之为快速分配策略。
    • 据我所知所有OpenJDK衍生出来的JVM都提供了TLAB的设计。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-aeR0gcyi-1597737846945)(E:\Users\zhoug\Desktop\y2.技术笔记\Java jvm\image\QQ截图20200817163558.png)]

TLAB分配过程:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1I35WIbZ-1597737846947)(E:\Users\zhoug\Desktop\y2.技术笔记\Java jvm\image\QQ截图20200817165218.png)]

11.堆空间常用的VM参数

  • -XX: +PrintFlagsInitial :查看所有的参数的默认初始值

  • -XX:+PrintFlagsFinal : 查看所有的参数的最终值(可能会存在修改,
    不再是初始值)

  • -Xms:初始堆空间内存 (默认为物理内存的1/64)

  • -Xmx:最大堆空间内存(默认为物理内存的1 /4)

  • Xmn: 设置新生代的大小。(初始值及最大值)

  • XX: +DoEscapeAnalysis ; 开启逃逸分析

  • XX:NewRatio:配置新生代与老年代在堆结构的占比

  • -XX:SurvivorRatio:设置新生代中Eden和SO/S1空间的比例

  • -XX:MaxTenuringThreshold:设置新生代垃圾的最大年龄

  • -XX: +PrintGCDetails:输出详细的GC处理日志

  • -XX:HandL ePromotionFailure:是否设置空间分配担保

    • [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0bCUcPa5-1597737846948)(E:\Users\zhoug\Desktop\y2.技术笔记\Java jvm\image\QQ截图20200818122625.png)]
  • -XX:+EliminateAllocations: 开启标量替换

12.通过逃逸分析看堆空间的对象分配策略

1.堆是分配对象存储的唯一选择吗?
  • 在《深入理解Java虚拟机》中关于Java堆内存有这样一段描述:
    随着JIT编译期的发展与逃逸分析技术逐渐成熟,栈上分配、标量替换优化技术将会导
    致一些微妙的变化,所有的对象都分配到堆上也渐渐变得不那么“绝对”了。
  • 在Java虚拟机中,对象是在Java堆中分配内存的,这是一个普遍的常识。但是,有一一
    种特殊情况,那就是如果经过逃逸分析(Escape Analysis) 后发现,一个对象并没有
    逃逸出方法的话,那么就可能被优化成栈上分配。这样就无需在堆上分配内存,也无须
    进行垃圾回收了。这也是最常见的堆外存储技术。
  • 此外,前面提到的基于openJDK深度定制的TaoBaoVM,其中创新的GCIH (GC
    invisible heap) 技术实现off -heap,将生命周期较长的Java对象从heap中移至
    heap外,并且GC不能管理GCIH内部的Java对象,以此达到降低GC的回收频率和提升
    GC的回收效率的目的。
2.逃逸分析概述
  • 如何将堆上的对象分配到栈,需要使用逃逸分析手段。
    这是一种可以有效减少Java程序中同步负载和内存堆分配压力的跨函数
    全局数据流分析算法。
  • 通过逃逸分析,JavaHotspot编译器能够分析出一个新的对象的引用的
    使用范围从而决定是否要将这个对象分配到堆上。
  • 当能够明确对象不会发生逃逸时,就可以对这个对象做一个优化,不将其分配到堆上,而是直接分配到栈上,这样在方法结束时,这个对象就会随着方法的出栈而销毁,这样就可以减少垃圾回收的压力。
  • 逃逸分析的基本行为就是分析对象动态作用域:
    • 当一个对象在方法中被定义后,对象只在方法内部使用,则认为没有
      发生逃逸。
    • 当一个对象在方法中被定义后,它被外部方法所引用,则认为发生逃
      逸。例如作为调用参数传递到其他地方中。
/**
 *  逃逸分析
 *
 *  如何快速的判断是否发生了逃逸分析,大家就看new的对象实体是否有可能在方法外被调用。
 */
public class EscapeAnalysis {

    public EscapeAnalysis obj;

    /*
    方法返回EscapeAnalysis对象,发生逃逸
     */
    public EscapeAnalysis getInstance(){
        return obj == null? new EscapeAnalysis() : obj;
    }
    /*
    为成员属性赋值,发生逃逸
     */
    public void setObj(){
        this.obj = new EscapeAnalysis();
    }
    //思考:如果当前的obj引用声明为static的?仍然会发生逃逸。

    /*
    对象的作用域仅在当前方法中有效,没有发生逃逸
     */
    public void useEscapeAnalysis(){
        EscapeAnalysis e = new EscapeAnalysis();
    }
    /*
    引用成员变量的值,发生逃逸
     */
    public void useEscapeAnalysis1(){
        EscapeAnalysis e = getInstance();
        //getInstance().xxx()同样会发生逃逸
    }
}

3.逃逸分析:代码优化

使用逃逸分析,编译器可以对代码做如下优化:

一、栈上分配

将堆分配转化为栈分配。如果一个对象在子程序中被分配,要使指向该对象的指针永远不会逃逸,对象可能是栈分配的候选,而不是堆分配。

二、同步省略:

如果一个对象被发现只能从一个线程被访问到,那么对于这个对象的操作可以不考虑同步。

三、分离对象或标量替换:

有的对象可能不需要作为一个连续的内存结构存在也可以被访问到,那么对象的部分(或全部)可以不存储在内存,而是存储在CPU寄存器中。
在这里插入图片描述
在这里插入图片描述

4.逃逸分析的缺点
  • 关于逃逸分析的论文在1999年就已经发表了,但直到JDK 1.6才有实现,而且这项技
    术到如今也并不是十分成熟的。
  • 其根本原因就是无法保证逃逸分析的性能消耗一定能高于他的消耗。虽然经过逃逸分
    析可以做标量替换、栈上分配、和锁消除。但是逃逸分析自身也是需要进行一系列复
    杂的分析的,这其实也是一个相对耗时的过程。
  • 一个极端的例子,就是经过逃逸分析之后,发现没有一个对象是不逃逸的。那这个逃
    逸分析的过程就白白浪费掉了。
  • 虽然这项技术并不十分成熟,但是它也是即时编译器优化技术中一个十分重要的手段。
    注意到有一些观点,认为通过逃逸分析,JVM会 在栈.上分配那些不会逃逸的对象,这
    在理论上是可行的,但是取决于JVM设计者的选择。据我所知,Oracle Hotspot
    JVM中并未这么做,这一点在逃逸分析相关的文档里已经说明,所以可以明确所有的
    对象实例都是创建在堆上。
  • 目前很多书籍还是基于JDK 7以前的版本,JDK已经发生了很大变化,intern字符串.
    的缓存和静态变量曾经都被分配在永久代上,而永久代已经被元数据区取代。但是,
    intern字符串缓存和静态变量并不是被转移到元数据区,而是直接在堆上分配,所以
    这一点同样符合前面一点的结论:对象实例都是分配在堆上。
  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值