JVM--堆内存结构

一、堆中的内存结构

堆内存结构

堆中内存分为两部分:新生代(young gen)和老年代(old gen),而新生代中又分为三部分:eden区、from区、to区,其中form区和to区又合称为生存区(survivor)
不特别指定的情况下,eden:from:to的内存比例为8:1:1

1.新生代

当我们实例化对象时,eden区内存足够,那么该对象会存储在eden区,内存不够的情况从左往右以此类推,寻找可以存放的区进行存储,找不到则会报OOM异常

我们可以使用JDK自带的工具:HSDB,来查看常量池的对象分布,我的JDK目录为:C:\Program Files\Java\jdk1.7.0_80,在lib目录下有个sa-jdi.jar,HSDB工具就在这个jar中,使用它之前,先要把jre\bin目录下的sawindbg.dll文件复制到C:\Program Files\Java\jre7\bin下

接下来看下我们的代码:

/**
 * Created by aruba on 2021/9/29.
 */
class DumpTest {

    public static void main(String[] args) {
        DumpTest eden = new DumpTest();

        try {
            Thread.sleep(Integer.MAX_VALUE);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

我们在主线程实例化一个对象,并无限等待

1.1 使用jps命令,查看jvm上跑的进程

jps

在这里插入图片描述

1.2 启动HSDB图形化界面

在JDK的lib目录下,打开命令行工具,执行下面命令:

java -cp ./sa-jdi.jar sun.jvm.hotspot.HSDB

打开后,选择Attach to HotSpot process,并输入DumpTest的进程id:8040
在这里插入图片描述

在下图中选择main线程,然后选择第二个选项
在这里插入图片描述

就可以看到我们对象在内存的哪里了
在这里插入图片描述

图太小了哈哈,可以看到图中DumpTest在新生代(YoungGen)区

2.老年代

老年代中存放着gc(JVM垃圾回收)15次后,还未能回收内存的对象,还存放着大对象和新生代内存不足,无法存放下的对象,我们修改下刚刚的代码,手动gc15次,再来看看对象处于哪个区

/**
 * Created by aruba on 2021/9/29.
 */
class DumpTest {

    public static void main(String[] args) {
        DumpTest eden = new DumpTest();


        for (int i = 0; i < 15; i++) {
            System.gc();
        }

        try {
            Thread.sleep(Integer.MAX_VALUE);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

再次执行jps命令,查看进程id,并使用HSDB工具查看,最后的内存为下图:
在这里插入图片描述

可以看到现在的DumpTest对象处于老年代(OldGen)区

二、minor GC与full GC

Java程序员不需要关注内存回收,是因为JVM中的垃圾收集器会自动收集垃圾对象,并释放它们的内存。对对象进行垃圾回收的操作,虽然后续出了更高性能的垃圾回收器,也无法避免性能的消耗,即所有GC都有stop the world。
GC分为两个部分:

minor GC:对堆中的新生代中所有对象进行一次垃圾回收,轻量级,该gc比较频繁,每回收一次,如果对象未被回收,那么标志位加1,当达到15时,对象进入老年代
full GC:对新生代和老年代进行一次gc,就是minor GC + Major GC,Major GC为老年代的gc,只有内存不够时,才会进行,比minor GC慢十倍以上
在这里插入图片描述

至此,JVM为什么把堆中分为新生代和老年代的原因可以得知,minor GC比较频繁,但是有些对象会长期存在内存中,不需要回收,所以对他进行gc检测是没有必要的,那么分为新生代和老年代,把内存分为临时创建的对象和较为常驻内存的对象可以优化性能

1.垃圾回收机制

1.1 不适用的引用计数算法

在这里插入图片描述

说到垃圾回收机制,很容易就能想到引用计数算法,当一个对象被其他对象引用时,他的引用计数就会加1,当一个对象的引用计数为0时,那么说明这个对象可以被回收了

面试中常问的一个问题:当A中有个对象引用B,当B中有个对象引用A,但没有别的对象引用它们,那么这两个对象能否被回收?
我们想要的答案很显然是能被回收,但是使用引用计数算法就无法回收它们了

1.2 可达性分析算法

在这里插入图片描述

该算法思想和最小生成树kruskal算法类似,每个对象都是一个顶点,有一些GC Roots顶点作为不可回收的顶点,当一个顶点的边的最远端无法到达GC Roots时,那么该顶点就可以被回收
使用该算法,即使发生1.1 中的情况,对象也可以被回收,因为两个对象并不是GC Roots树下的节点

三、垃圾回收算法

新生代和老年代也有一系列的垃圾回收算法,其中新生代分为三个部分:
以下不考虑内存不够的情况

eden:新创建的对象存放的位置
from(s0):复制算法相关
to(s1):复制算法相关

1.标记-清除算法

最基础的算法,分为两个步骤:
1.对可以被回收的对象进行标记
2.对标记的对象进行内存回收
在这里插入图片描述

优点:简单
缺点:标记和回收效率不高,并且造成大量内存碎片,导致申请大内存对象时内存不够现象,导致触发full GC

2.复制算法

复制算法为了解决内存碎片问题,将内存分成1:1的两块,每次只使用一块,当这一块的内存用完了,就将还存活着的对象复制到另外一块上面,然后再把已使用过的内存空间一次清理掉。
在这里插入图片描述

优点:每次都是对整个半区进行内存回收,内存分配时也就不用考虑内存碎片等复杂情况,只要移动堆顶指针,按顺序分配内存即可,实现简单,运行高效。
缺点:内存只有一半

上面提到eden:from:to的内存为8:1:1,因为使用的是复制算法
当回收时,将Eden和Survivor中还存活着的对象一次性地复制到另外一块Survivor空间上,最后清理掉Eden和刚才用过的Survivor空间

3.标记-整理算法

老年代不能接收内存浪费,所以不使用复制算法,标记-整理算法分为两个步骤:
1:标记过程和标记-清楚算法相同,对可以被回收的对象进行标记
2:整理是让所有存活的对象都向一端移动,然后直接清理掉端边界以外的内存(双指针算法,一次遍历)
在这里插入图片描述

优点:内存连续
缺点:低效

4.分代收集算法

分代收集算法就是对内存进行划分,各个垃圾收集器不太一样,本文基础标准HotSpot虚拟机,一般是分为新生代和老年代,针对不同的内存区域,使用不同的算法,在新生代中,每次垃圾收集时都发现有大批对象死去,只有少量存活,那就选用复制算法,只需要付出少量存活对象的复制成本就可以完成收集。而老年代中因为对象存活率高、没有额外空间对它进行分配担保,就必须使用“标记—清理”或者“标记—整理”算法来进行回收。

四、垃圾回收器

垃圾回收器分为很多种,并不断有新的垃圾回收器出现,很多公司拥有自己编写的垃圾回收器
在这里插入图片描述

stop the world:所有垃圾回收器都避免不了stop the world带来的延迟,试想下,如果我们要对内存进行一次扫描,挑出可以被回收的对象,那么此时的内存还能不能变化?如:实例化一个对象分配新的内存,很显然我们是不希望内存变动的,也就是说stop the world必须暂停所有线程

五、引用类型

Java中的引用对象分为四种:
强引用:不能被gc回收
软引用:当内存不够时,发生gc,优先被回收
弱引用:gc发生时,立刻被回收
虚引用:创建一个虚引用对象后,返回的都是一个null,用来检测gc发生情况

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值