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是如何进行垃圾回收的。
- 当Eden区域的内存达到一定的阈值后,开始从root应用链条中找出任然存活的对象,然后标记出来
- 将存活的对象移复制到survior区中的To区
- 将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代码的执行加深印象。