Java 运行时数据区域
1 程序计数器:是一块较小的内存空间,可以看成,当前线程执行字节码文件的行号指示器,如图可知,这部分是不共享的数据区,如果执行的是Java代码,这个计算器记录的是 正在执行的虚拟机字节码指令地址,如果执行的是Native方法,这个计数器的值为空,该区域是唯一没有内存泄漏问题的区域
2 虚拟机栈:也是线程私有的,生命周期与线程相同,描述的是Java方法执行的内存模型,每个方法执行的同时也会创建一个栈帧(方法运行时的基本数据结构),每个方法从调用到执行完成的过程,就对应着一个栈帧从入栈到出栈的过程,该区域规定了两种异常
a:线程请求的深度大于虚拟机所允许的深度,StackOverFlowError
b:如果虚拟机可以动态扩展(大部分虚拟机都可以)如果扩展时,无法申请到足够的内存,OutOfMemoryError异常
3 本地方法栈:线程私有,与虚拟机栈十分的类似,不同点在与,该区域是用来请求Native方法服务的,有的虚拟机甚至将两者合二为一,异常与2一致StackOverFlowError OutOfMemoryError
4 堆:堆是Java虚拟机管理的最大一块的内存空间,被所有线程共享的一块内存区域,此区域最大的作用就是用来存放对象的实例(new xxx),是Java垃圾回收机制主要管理的区域,也叫GC堆(垃圾堆),从内存回收的角度来看,Java堆可以分为新生代,老年代,如果在堆中没有内存来分配实例,且无法再扩展将会抛出OutOfMemoryError异常
5 方法区:方法区也是线程共享的区域,它用于存储已经被线程加载的类信息,常量,静态常量,即时编译器编译过的代码,Java虚拟机规范把方法区描述成堆的一个逻辑,但是它却有一个别名,叫做Non-Heap(非堆),目的应该是要与堆分开来,当方法区无法满足内存的分配时,会跑出OutOfMemoryError
6 运行常量池:是方法区的一部分,class文件中,除了有类的版本,字段,方法,接口描述等,还有一项信息,常量池,用于存放编译期生成的各种字面量和符号的应用,这部分的内容加载进入方法区在常量池中存放,既然是方法区的一部分,那么也受到方法区的限制,当方法区无法满足内存的分配时,会跑出OutOfMemoryError
字面量:int i=3 3就是这个int类型的字面量 String s=“abc” abc就是这个s的字面量
hotsport虚拟机对象探秘
对象的创建
1 虚拟机遇到一个new指令
2 检查指令的参数,是否在常量池中找到一个类的符号应用,并且检查这个符号代表的类是否已经被加载过了,没有,先加载类(TODO,类加载后续再说)有,分配内存
3 为新生对象分配内存,对象所需的内存大小在类加载之后,完全可以确定(TODO)
内存绝对规整:用过放一边,空闲放一边,中间有指针作为分界点的指示器,那么分配的内存仅仅是把指针向空闲那一边挪动一段与对象大小相等的距离,称为指针碰撞
内存不规整:已用和空闲的相互交错,没有办法指针碰撞,虚拟机就要维护一个列表,记录一下,哪块是可用,哪块不可用,分配的时候,从列表上找一块足够大的内存分配称为空闲列表
问题来了?并发的情况下,给A分配内存,还没指过去,B就占用了怎么办?
方法一:分配内存空间也做同步处理+失败重试机制,保证原子性
方法二:把内存分配的动作,按照线程,分在不同的空间之中,即,每个线程先分一块
4 内存分配完了之后,虚拟机将分配到的内存全部清零,这样就保证了字段实例,在Java代码中,不用初始化,就可以使用
5 接下来,虚拟机要对对象做一些设置,比如,这个对象,是哪个类的实例,对象的哈希码,这些信息存放在对象头上
6 以上动作完成之后,从虚拟机的视角看,一个新的对象就产生了,但从Java视角看,对象创建才刚刚开始,init方法还没有执行,对象中所有的字段还是0,完成之后,才是一个真正的对象
对象的内存布局
在hotspot虚拟机中,分为三个区域:对象头、实例数据、对齐填充
对象头
- 运行时数据
- 内容举例:
- 哈希码HashCode
- GC分代年龄
- 锁状态标识
- 线程持有的锁等
- 长度(未开启压缩指针下):32 or 64 位的虚拟机中分别为 32bit or 64 bit
- 官方名称:Mark Word
- 非固定数据结构:
- 考虑到存储效率,在极小空间内存储更多信息,长度是不确定的
- 内容举例:
- 类型指针
- 作用:对象指向它的类元数据的指针,JVM通过此来确定该对象是哪个类的实例
- 并非所有JVM的实现都需要这个指针,也就是对象的元数据查找并不一定要经过对象本身
- 作用:对象指向它的类元数据的指针,JVM通过此来确定该对象是哪个类的实例
注意:对于数组而言,对象头中还会记录数组的长度。JVM可以通过对象的元数据信息确定Java对象的大小。但从数组对象的元数据中是无法获取数组大小的。
实例数据
- 作用:对象真正存储的有效信息,也是在代码中所定义的各种类型的字段内容
- 内容:无论是从父类继承下来的,还是在子类中定义的,都要记录
- 存储顺序(HotSpot)
- 策略:同宽度者分配到一起
- 具体分配方式:
- longs/doubles
- ints
- shorts/chars
- bytes/booleans
- oop(Ordinary Object Pointers)
对齐填充部分
- 作用:占位符
- 并非必须存在
- HotSpot要求对象大小是8字节的整数倍
- 头部分正好符合要求,当实例数据部分没有对齐时,就需要通过对齐补充来补全
新老生代
新生代
堆中 存放那些很快被GC回收掉的/不是特别大的对象 采用复制算法
3个区(较大的Eden,两个较小的Survivor Eden:Survivor = 8:1)
发生在新生代的GC为Minor GC
JVM 无法为一个新的对象分配空间时会触发 Minor GC
在Minor GC时会将新生代中还存活着的对象复制进一个Survivor中,然后对Eden和另一个Survivor进行清理。所以,平常可用的新生代大小 为Eden的大小+一个Survivor的大小。
老年代
堆中 老年代则是存放那些在程序中经历了好几次回收仍然还活着或者特别大的对象
采用 标记-清除算法,这两个算法主要看虚拟机采用的哪个收集器
在老年代中的GC则为Major GC
Full GC 是清理整个堆空间—包括年轻代和老年代。
永久代
JVM的方法区,也被称为永久代。在这里都是放着一些被虚拟机加载的类信息,静态变量,常量等数据。这个区中的东西比老年代和新生代更 不容易回收。
那么什么情况下,新生代的对象会进入老年代呢?
1 新生代存活的对象大于Survivor的大小时,这时一个Survivor装不下它们,那么它们就会进入老年代。
2 如果设置了-XX:PretenureSizeThreshold3M 那么大于3M的对象就会直接就进入老年代。
3 在新生代的每一次Minor GC 都会给在新生代中的对象+1岁,默认到15岁时就会从新生代进入老年代,可以通过-XX: MaxTenuringThreshold来设置这个临界点。老年代中的对象比新生代中的对象不易回收许多。
来源:oschina
链接:https://my.oschina.net/u/3523594/blog/1595246