JVM学习-堆

一、概述

1、概念

  • 一个JVM实例只存在一个堆空间,它也是 Java 内存管理的核心区域
  • 堆在启动时被创建,大小也被确定了
  • 所有线程共享堆空间数据。但是在堆空间上为每个线程划分了各自对应的私有的缓冲区(Thread Local Allocation Buffer,TLAB)
  • 在 hotspot 虚拟机中所有的对象都是分配在堆空间中的

2、堆空间细分

  • java7 之前:新生代+老年代+永久区
  • java8 及以后:新生代+老年代+元空间
  • 设置堆空间大小:-Xms 堆空间起始内存,-Xmx 堆空间最大内存。默认起始内存为 电脑物理内存/64,最大内存为 电脑物理内存/4。(这里设置的为 新生代 + 老年代的内存大小)

3、新生代与老年代

  • 新生代分为:Eden 空间、Survivor0 空间、Survivor1 空间 (也叫 from 区、to 区)
  • 默认新生代、老年代的比例为 1: 2,即 新生代占整个对空间的 1/3。-XX:NewRatio=2 (默认)
  • 在新生代中 Eden :Survivor0 :Survivor1 为 8:1:1。-XX:SurvivorRatio=8 (默认)

4、对象分配过程

                  

如上图所示(没有按实际比例)

  1. new 的对象先放在 Eden 中,放不下进行 Minor GC,还放不下放到老年代,老年代放不下则进行Major GC/Full GC,如果还是放不下则 OOM
  2. Eden 满的时候进行 Minor GC (Young GC)将 Eden 区中没有被其他对象所引用的对象销毁,幸存的对象放入 幸存者0区(Survivor0),Survivor0 区中对象的年龄计数 + 1。如果 Survivor区放不下,放入老年代。
  3. 当 Eden 再次满的时候继续进行 Minor GC,将 Eden 区、Survivor0 区中没有被其他对象所引用的对象销毁,幸存的对象放入 Survivor1 区。并将 幸存的对象年龄计数 +1。如果对象的年龄 > 15,则放入老年代 。
  4. 当 老年代 满的时候进行 Major GC (Old GC)对老年代进行内存清理
  5. 如果老年代 GC 后任然没有空间,则 OOM

总结:频繁在新生代进行垃圾回收,很少在老年代进行,几乎不在永久代/元空间收集

5、TLAB

  1. 在 Eden 区中为每个线程分配了一个私有的缓存区域,包含在 Eden 区中
  2. 产生原因:堆空间是所有线程共享的,在并发环境下为了避免多个线程同时操作同一个地址,需要使用加锁机制,进而影响分配速度。使用 TLAB 可以避免一系列的非线程安全问题,同时还能提升内存分配的吞吐量,我们将这种内存分配方式称为快速分配策略
  3. JVM 将 TLAB 作为内存分配的首选,一旦对象在 TLAB 中分配失败,JVM 就会通过使用锁机制确保数据操作的原子性,从而直接在 Eden 空间中分配内存
  4.  TLAB 空间非常小,只占 Eden 的 1% 

                               

6、逃逸分析

当一个对象在方法中被定义后,对象只在方法内部使用,则认为没有发生逃逸。

7、标量替换

在JIT阶段,如果经过逃逸分析,发现一个对象不会被外界访问的话,那么经过 JIT 优化,就会把这个对象拆解成基本的变量来替换。

public class JavaTest {
    public static void main(String[] args) {
        add();
    }

    public static void add() {
        User user = new User("张三", 20);
        System.out.println("name:" + user.getName() + "age:" + user.getAge());
    }
}

class User {
    private String name;
    private Integer age;

    public User(String name,Integer age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public Integer getAge() {
        return age;
    }
}

经过逃逸分析发现,User 对象只在 add() 方法中使用,没有发生逃逸,这时 JVM 就会进行标量替换。重新替换后的 add () 方法如下:

public static void add() {
        String name = "张三";
        int age = 20;
        System.out.println("name:" + name + "age:" + age);
    }

经过标量替换后,两个变量就会被分配在栈帧中的局部变量表。这样就减少了堆空间的内存占用

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值