深刻理解Java虚拟机(笔记,更新中)

Java 运行时数据区域

1 程序计数器:是一块较小的内存空间,能够当作,当前线程执行字节码文件的行号指示器,如图可知,这部分是不共享的数据区,若是执行的是Java代码,这个计算器记录的是 正在执行的虚拟机字节码指令地址,若是执行的是Native方法,这个计数器的值为空,该区域是惟一没有内存泄漏问题的区域算法

2 虚拟机栈:也是线程私有的,生命周期与线程相同,描述的是Java方法执行的内存模型,每一个方法执行的同时也会建立一个栈帧(方法运行时的基本数据结构),每一个方法从调用到执行完成的过程,就对应着一个栈帧从入栈到出栈的过程,该区域规定了两种异常数组

a:线程请求的深度大于虚拟机所容许的深度,StackOverFlowError数据结构

b:若是虚拟机能够动态扩展(大部分虚拟机均可以)若是扩展时,没法申请到足够的内存,OutOfMemoryError异常并发

3 本地方法栈:线程私有,与虚拟机栈十分的相似,不一样点在与,该区域是用来请求Native方法服务的,有的虚拟机甚至将二者合二为一,异常与2一致StackOverFlowError  OutOfMemoryErroroop

4 堆:堆是Java虚拟机管理的最大一块的内存空间,被全部线程共享的一块内存区域,此区域最大的做用就是用来存放对象的实例(new xxx),是Java垃圾回收机制主要管理的区域,也叫GC堆(垃圾堆),从内存回收的角度来看,Java堆能够分为新生代,老年代,若是在堆中没有内存来分配实例,且没法再扩展将会抛出OutOfMemoryError异常布局

5 方法区:方法区也是线程共享的区域,它用于存储已经被线程加载的类信息,常量,静态常量,即时编译器编译过的代码,Java虚拟机规范把方法区描述成堆的一个逻辑,可是它却有一个别名,叫作Non-Heap(非堆),目的应该是要与堆分开来,当方法区没法知足内存的分配时,会跑出OutOfMemoryErrorspa

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能够经过对象的元数据信息肯定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来设置这个临界点。老年代中的对象比新生代中的对象不易回收许多。

相关文章
相关标签/搜索