【数据结构与算法之美】什么是数组?引出jvm垃圾回收

目录

一、为什么很多编程语言的数组都是从0开始编号的?

二、什么是数组?

三、数组和链表的面试纠错?

四、容器是否完全替代数组?

五、标记清除算法?

六、警惕数组的访问越界问题

七、课后思考

1.  描述 Java 语言中 JVM 的标记清除垃圾回收算法。

1)判断对象是否存活:

2)分代回收

3)可以作为GC Roots的对象

4)JVM垃圾收集器分类:

2. 根据一维数组的内存寻址公式,写出二维数组的内存寻址公式。


一、为什么很多编程语言的数组都是从0开始编号的?

1、从数组存储的内存模型上来看,“下标”确切的说法就是一种“偏移”,相比从1开始编号,从0开始编号会少一次减法运算,数组作为非常基础的数组结构,通过下标随机访问元素又是非常基础的操作,效率的优化就要尽可能的做到极致。
2、主要的原因是历史原因,C语言的设计者是从0开始计数数组下标的,之后的Java、JS等语言都进行了效仿,或者说是为了减少从C转向Java、JS等的学习成本。

二、什么是数组?

数组是一个线性数据结构,用一组连续的内存空间存储一组具有相同类型的数据。
其实数组、链表、栈、队列都是线性表结构;树、图则是非线性表结构。

三、数组和链表的面试纠错?

1、数组中的元素存在一个连续的内存空间中,而链表中的元素可以不存在于连续的内存空间。
2、数组支持随机访问,根据下标随机访问的时间复杂度是O(1);链表适合插入、删除操作,时间复杂度为O(1)。

四、容器是否完全替代数组?

容器的优势:对于Java语言,容器封装了数组插入、删除等操作的细节,并且支持动态扩容。
对于Java,一些更适合用数组的场景:
1、Java的ArrayList无法存储基本类型,需要进行装箱操作,而装箱与拆箱操作都会有一定的性能消耗,如果特别注意性能,或者希望使用基本类型,就可以选用数组。
2、若数组大小事先已知,并且对数组只有非常简单的操作,不需要使用到ArrayList提供的大部分方法,则可以直接使用数组。
3、多维数组时,使用数组会更加直观。

五、标记清除算法?

GC最基础的收集算法就是标记-清除算法,如同他们的名字一样,此算法分为“标记”、“清除”两个阶段,先标记出需要回收的对象,再统一回收标记的对象。不足有二,一是效率不高,二是产生碎片内存空间。

六、警惕数组的访问越界问题

C 语言代码的运行结果分析

int main(int argc, char* argv[]){
    int i = 0;
    int arr[3] = {0};
    for(; i<=3; i++){
        arr[i] = 0;
        printf("hello world\n");
    }
    return 0;
}

函数体内的局部变量存在栈上,且是连续压栈。在Linux进程的内存布局中,栈区在高地址空间,从高向低增长。变量i和arr在相邻地址,且i比arr的地址大,所以arr越界正好访问到i。当然,前提是i和arr元素同类型,否则那段代码仍是未决行为。

这段代码无限循环原因有二,以及一个附加条件:

1. 函数体内的局部变量存在栈上,且是连续压栈, 栈空间从高往低依次分配,i占4字节,接着arr占12字节,内存从高往低是这样:存i的4字节|arr[2]|arr[1]|arr[0],数组访问是通过“baseAddr+index乘typeSize”得到,算下来当index=3时,刚好是i的地址
2. 这里刚好满足字节对齐,系统为64位系统,字长64,那么字节对齐必须是8字节的倍数,刚好i变量和arr变量占了16字节,对齐了;如果这里将arr[3]改为arr[4],为了对齐,内存从高往低是这样:存i的4字节|空4字节|arr[3]|arr[2]|arr[1]|arr[0],那么arr[4]刚好是空的4字节,无法影响到i的值,则并不会无限循环
3.附加条件:编译时gcc默认会自动添加越界保护,此处要达到无限循环效果,编译时需加上-fno-stack-protector去除该保护

七、课后思考

1.  描述 Java 语言中 JVM 的标记清除垃圾回收算法。

解答:

1)判断对象是否存活:

采用可达性分析算法来判断对象是否存活,会遍历所有 GC ROOTS,将所有 GC ROOTS 可达的对象标记为存活

2)分代回收

(1)年轻代:复制算法,8:1:1的比例eden区和两个survivor(survivor0,survivor1)区;

(2)老年代:标记整理

  1. 标记阶段:首先进行标记;
  2. 清除阶段:将存活的对象往一端移动,最后直接清除另一段端空间,不会造成对应内存碎片问题
  3. 不足:标记和清理效率都不高,少量垃圾产生时才会高效;

3)可以作为GC Roots的对象

  1. 虚拟机(栈帧中的本地变量表)中引用的对象
  2. 方法区中类静态属性引用的对象
  3. 方法区中常量引用的对象
  4. 本地方法栈中JNI(即一般说的native方法)中引用的对象

4)JVM垃圾收集器分类:

(1)新生代

Serial (第一代)

PraNew (第二代)

Parallel Scavenge (第三代)

G1收集器(第四代) JDK1.7后

(2)老年代

Serial Old (第一代)

Parallel Old (第二代)

CMS (第三代)

G1收集器 (第四代) JDK1.7后

2. 根据一维数组的内存寻址公式,写出二维数组的内存寻址公式。

解答:

一维数组:a[i]_address=base_address+i*type_size
二维数组:二维数组假设是m*n, a[i][j]_address=base_address + (i*n+j)*type_size
三维数组:三维数组假设是m*n*q, a[i][j][k]_address=base_address + (i*n*q + j*q + k)*type_size

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值