java的“堆”内存模型

java的堆内存模型

这里只说下java的堆内存模型,以及GC的分代收集的原因(为什么不直接使用某一种收集方法,比如复制算法同事GC yong和old)

java堆内存模型

对于初学java的人来说,对于对的理解就是,堆里面存放对象,数组。但是有没有想过真正的对象到底是个什么样子,对象里面到底是啥,为了解答这个问题,先来看下java当中堆的内存模型。

                                                  图1 java中的堆内存模型与java的GC分代机制

概述:

通过上面的图可以看出,yong区域和tenured区域共同组成了java的堆,yong区域又可以细分为Eden区域和两个suvrior区,也就是说以上的部分功能组成了java的堆区。这个庞然大物里面存放的都是对象(数组本节不讨论)。

java对象结构

上面说了java的堆的结构,下面说下java堆里面存放的元素:对象

我们有没有想过这样的一个问题:java程序中在new对象之后,老师通常会告诉我们,这个时候new出来的对象就放到堆里面了,那么这个对象到底是个啥。

public class Student {
    private String name;
    private int age;
    private String gender;
    private String address;

public Student(String name, int age, String gender, String address) {
        this.name = name;
        this.age = age;
        this.gender = gender;
        this.address = address;
    }
    
    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public String getGender() {
        return gender;
    }

    public void setGender(String gender) {
        this.gender = gender;
    }

    public String getAddress() {
        return address;
    }

    public void setAddress(String address) {
        this.address = address;
    }

这里是一个很普通的javaBean,当我们在new一个student的时候,会将这个对象 的属性全部赋值,然后返回这个对象的堆内存地址。当我们再创建n个对象的时候,只是属性不同,其他的没有任何区别,这个对象对应的类名、方法名都没有变化,唯一变化的只有属性。你可能回想当我们使用student对象调用方法的时候会根据这个对象的地址找到方法然后执行,所以可能误以为这个方法在对象里面存储。那么这样依赖每次new对象的时候是不是都需要存储一模一样的方法呢,结果造成了存储冗余,实际上方法并没有存储再对象中。看下对象的结构。

public static void main(String[] args) {
        Student student1 = new Student("sam",23,"male","America");
        Student student2 = new Student("mary",23,"femal","China");
    }

这里新创建了两个student对象。根据上面的推论,java不会存储重复数据。下面给出java对象的正确存储解释。

上面这张图以及很形象的说了对象到底是个啥,简言之,对象主要用来存放实例数据,剩余的最重要的部分就是class Poiner指针,它指向了java8 的metadata区域/java7的perm区域也叫方法区。这里面的instanceKlass对象实际是一个c++的对象,它只是对于class字节码的一种存储结构,即java源代码再编译完成后得到的class文件,在经过类加载进入内存后,这个class字节码中的类信息(方法表、字段表)全部存储在这个c++的数据结构里面。同时还会有一个mirror的java镜像对象被创建。

 

GC分代的设计原因(回答为什么性能高)

这里结合下图说一下java是如何进行垃圾回收的。

  1. 当Eden区域的内存达到一定的阈值后,开始从root应用链条中找出任然存活的对象,然后标记出来
  2. 将存活的对象移复制到survior区中的To区
  3. 将From区中任然存活的对象进行判断:
  • 如果分代的年龄(对象头部)达到了tenured的阈值,则判定为老年代的对象,直接移动到tenured
  • 如果分代没有达到tenured的阈值,则也移动到To区域

      4.交换From(已经clear)区域和To区域,清空dead对象。yong GC完成。等待下次将From中的存活对象赋值到其他区域。

      

                                                  图1 java中的堆内存模型与java的GC分代机制

解释分代的原因:

想解释这个,只需要看下如果不进行分代收集,那么会有怎样的现象呢?

                                                                 图  不分代收集的清空下  一次GC情况

图中红色代表老年代,蓝色代表新生代,复制收集后,即采用复制算法收集后,蓝色块减少,这表示有较多的新生对象被clear掉了,红色对象(老年代)都被移动到了新的内存区域中了。

如果java不采取不分代收集的情况下,会有如下的弊端

不分代收集

Yong+Tenured分代收集

内存利用率=50%

内存利用率>>50%

Yong+Tenured>阈值,触发fullGCC,频率高

Tenured内存中>阈值,触发fullGC,频率低

fullGC频率高,服务性能差

fullGC频率高,服务性能差

解释

1内存利用率

不分代收集的话,那么由于堆区会经常进行对象的eden(造),所以很快就会达到要GC的阈值,因此选择复制法进行收集,但是这样一来,内存就只有50%的利用率。

如果分代收集的话,eden是100%,只有两个surviour(from和to区是50%),利用率提高

2触发fullGC的频率

表格中很清楚,由于两个收集区域中的内存中既有新生代也有老年代,由于新生代对象很多,所以很快就会发生FullGC

但是分代收集就不一样了,分代收集只有在tunered区域中的old对象达到阈值的时候才会引发fullGC,对于频繁的新生代创建来说,在yong区域能够在很快时间内实现GC,时间较短,性能较高。

3 关于新能这块其实还是比较笼统,这里具体说就是:分代收集的时候,对于频繁创建的对象,在yong收集,由于yong中存活的对象较少,因此复制少量的存活对象是很快的,所以分代收集中的yongGC时间短,效率高。

但是不分代的情况下;每次都需要移动old对象,而且old对象中有很多是大对象,这就必然增大了开销,所以不分代的FullGC性能差,并且再加上不分代的情况下fullGC的频率高,那么性能就更加差了。

 

参考

第一:java程序是如何到达汇编指令最终到cpu的010101二进制命令的

http://www.sohu.com/a/307082901_120103006,结合OS讲解的和透彻

第二:java8中的元空间到底是个啥

https://blog.youkuaiyun.com/qq_31489805/article/details/78513407

其实就是存储了类对象和对应的加载器,可以理解为就剩下class对象了  原来方法区域中的常亮池等东东全部移动到heap中了

https://blog.youkuaiyun.com/qq_31489805/article/details/78513407

通过汇编的部分知道class是如何被执行的了

通过元空间知道java8和以前的内存是如何将class中的代码加载到内存中的了,同时也复习了相关的操作系统的知识,对java代码的执行加深印象。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值