Java的基础入门学习01-基本内存原理

Java基础入门学习

一、java的基本内存存储

1.1、java中的堆,栈以及方法区


栈就是方法栈,是方法运行时使用的内存,例如main方法在运行时就需要进入栈,而方法在栈中的存储方式则是Stack的经典存储形式,先进后出,类似于子弹压入弹匣,表现在代码中则是最后被调用的方法先运行,最先调用的方法最后运行,这里举个实例:

public class JavaTest1 {
    public static void main(String[] args) {
        eat();
    }
    public static void eat(){
        study();
        System.out.println("吃饭");
    }
    public static void study(){
        System.out.println("吃完后学习");
    }
}

运行后的结果是
在这里插入图片描述
在栈中表现为
在这里插入图片描述
这就是栈的基本内存原理


堆是用来存储对象以及数组,可以说是存储实例对象的内存,堆本身的存储是根据地址存储的,即当你创建了一个 对象,会生成对应的对象地址,在每个引用对象的地方都会记录这个地址,在需要使用的时候,就会到堆区域去查找对应地址的数据,这样就可以得到对象的值。举例说明则是:

public class JavaTest2 {
    public static void main(String[] args) {
        int[] arr = new int[2];
        System.out.println(arr);
        arr[0] = 1;
        arr[1] = 2;
        System.out.println(arr[0]);
        System.out.println(arr[1]);
    }
}

在这个实例中我创建了一个数组,那么在创建成功时,这个arr变量本身就应该记录的是数据在堆内存中的地址,所以我们可以先打印arr来看,找到地址以后,我们取地址中的值,自然就可以正常打印出来,这就类似于知道自己家的地址,和到家以后进入家里是两件事情。代码运行的结果如下:
在这里插入图片描述
第一个字符串就是地址,后面则是对应的数据值
方法区
方法区则是用来存储可以可以运行的class文件,每次想要运行一个方法,首先需要加载方法所在的class文件,加载完成后你的JVM才能检测到对应的方法,例如上述的两个实例,在运行main方法之前,方法区中就会去加载JavaTest1的class文件以及JavaTest2的class文件

1.2 对象内存图

为了更方便理解内存原理,举个实例,首先创建一个Student类

public class Student {
    private String name;
    public void study(){
        System.out.println("学生在学习");
    }
}

然后我们在另一个测试类中去引入使用Student类

public class JavaTest3 {
    public static void main(String[] args) {
        Student s = new Student();
        s.study();
    }
}

那么在这个情况中,我们在测试类运行main方法时,方法区应该首先去加载JavaTest3的class文件然后这个时候就会在栈区域压入JavaTest3方法中的main方法,此时在main方法中发现创建了Student对象,那么此时就会在方法区中载入Student的class文件,并且此时创建了一个对象,会将对象的地址记录在变量中,并且在堆中开辟一个新空间,发现调用study方法后将Student类中的study方法压入栈中,最后在栈中依次调用方法。简单逻辑示意图如下:
在这里插入图片描述

二、java的内存回收机制

2.1 java内存回收机制的说明

如果对c或者c++有一定基础的话,都会知道在创建对象之后需要利用delete函数来释放资源,也就是说我们在使用完对象以后,要把对象删除,这样才不会使得使用完之后的对象一直占有内存,最终导致内存泄漏。而java中并没有类似的函数时因为java中有独特自动清除不需要的垃圾,这就是内存回收机制。

2.2 如何定位垃圾

2.2.1 引用计数法

顾名思义,该方法是利用计数器,每一个内存对象都会有一个计数器,当对象正在引用时,该计数器+1,而引用失败则-1,那么当计数器为0时说明该对象已经无人使用,那么在java底层中,它会定期的去查询这个计数器,当发现计数器为0时,则判定该对象是垃圾实现定位功能。
缺陷:无法解决循环利用问题,也就是说假设A和B都是垃圾,但是A引用了B,B又引用了A,那么此时二者的计数器不可能为0,也就无法被找到清除

2.2.2 可达性分析方法(链式查询)

这里类似于数据结构中的二叉树,这里把JVM线程栈,本地方法栈,运行时常量池,方法区的静态引用,类对象这些东西定义成根,main()方法启动的哪些内容,就是根,顺着根的引用去查询能够引用达到的就是需要使用的,无法达到的就是垃圾,那么这些无法达到的地方就会被标记,实现定位功能,java后期更喜欢采用此方法

2.3 垃圾清除方法

2.3.1 标记-清除算法

这种算法很简单就是直接回收已经标记的对象,释放内存。
在这里插入图片描述
但是这种方法会导致可用内存碎片化,在图上体现为出现不连续的方格,那么当我们存储占据内存较多的对象时,无法存入碎片化的可用内存中,就会导致资源的浪费。

2.3.2 标记-复制

在这里插入图片描述
这种情况就是准备一个规模大小一致的内存空间,将这一份专门用来复制,清除回收完成后将可用区域单独存储出来。但是这样子为了准备完全可供复制的空间,会消耗大量的内存。

2.3.3 标记-压缩

在这里插入图片描述

这种算法就是一边回收内存,一边压缩,使得内存完整,但是这个算法的效率很低,因为同时需要回收又同时需要压缩。
三种算法都存在一定的问题所以JVM就综合使用三种算法,产生了各种各样的GC垃圾回收器。

2.4 垃圾回收器

2.4.1 早期Serial回收器

这里为了方便大家理解,借鉴引用一下b站北京即刻就业博主的讲解图片
在这里插入图片描述
Serial回收器是单线程的回收器,Serial工作在年轻代(也就是新创建不久的对象),需要搭配对应的在老年代工作的Serial Old,Serial回收器的方式是,停掉当前所有的用户线程,然后执行垃圾回收线程,这个过程被称为STW,全称也就是Stop The World。但是这种方式因为需要停掉所有的用户线程,容易造成长时间的堵塞。

2.4.2 中期Parallel Scavenge回收器

Parallel Scavenge回收器是多线程的回收器。同样的搭配Parallel Old使用,相较于上述的Serial回收器,在回收过程中依然触发STW,但是垃圾回收线程是多线程回收,加速了垃圾处理速度。
在这里插入图片描述

2.4.3 过渡CMS并发回收器

CMS工作在老年代,与它搭配的新生代是ParNew
在这里插入图片描述
在CMS中它依旧会触发STW,但是在初始标记阶段只标记根下的第一层,然后开始边清理垃圾,边标记,但是在这个情况下,由于一边清除垃圾,一边新增垃圾,有可能出现错标,漏标的情况,所以它还会触发一次STW来重新标记垃圾,最后并发清除,此时用户线程运行的时候产生的垃圾无法被清除只能等待下一波清理,这些垃圾被称为浮动垃圾。

2.4.4 现在G1垃圾回收器

在jdk1.7以后就使用了G1垃圾回收器,它不需要搭档,它允许用户自己设置预期的STW时间,但是不一定符合预期,只能尽量靠近时间,里面的内存不是规定划分年轻代或者老年代,其中区域被年轻代使用就是年轻代,清空后被老年代使用就是老年代,它只需要扫描对应的区域,不需要扫描全部区域。
在这里插入图片描述
并且在清理上也采用了复制算法,确保内存不会碎片化,会把需要的内存都复制到一个新区域。

整体流程上跟CMS没有大区别,但进一步解决了错标问题,如果想深入了解可以学习三色标记算法。
博主现在正在巩固java基础来加强水平,希望对大家有帮助。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值