JVM

jvm内存区域

  1. 线程隔离的数据区

    • 程序计数器
      程序计数器(Program Counter Register)是当前线程所执行的字节码的行号指示器,字节码解释器工作时就是通过改变这个计数器的值来选取下一条需要执行的字节码指令。分支、循环、跳转、异常处理、线程恢复等基础功能都需要依赖这个计数器来完成。
      为了线程切换后能恢复到正确的执行位置,每条线程都需要有一个独立的程序计数器,各条线程之间计数器互不影响,独立存储,所以程序计数器这类内存区域为“线程私有”的内存
      如果线程正在执行的是Native方法,这个计数器值则为空(Undefined)。
      native是与C++联合开发的时候用的!使用native关键字说明这个方法是原生函数,也就是这个方法是用C/C++语言实现的,并且被编译成了DLL,由java去调用
    • java虚拟机栈
    • 本地方法栈
      他们作用相似,区别只是:虚拟机栈为虚拟机执行Java方法(也就是字节码)服务,而本地方法栈则为虚拟机使用到的Native方法服务。
      每个方法在执行的同时都会创建一个栈帧(Stack Frame)用于存储局部变量表、操作数栈、动态链接、方法出口等信息。每一个方法从调用直至执行完成的过程,就对应着一个栈帧在虚拟机栈中入栈到出栈的过程。
      程序员人为的分为“堆栈”中的“栈”。

    栈的存储:
    栈里存放了编译期可知的各种基本数据类型(boolean、byte、char、short、int、float、long、double)、对象引用和指向了一条字节码指令的地址。
    局部变量表所需的内存空间在编译期间完成分配,其中64位的long和double类型的数据会占2个局部变量空间,其余的数据类型只占用1个。当进入一个方法时,这个方法需要在帧中分配多大的局部变量空间是完全确定的,在方法运行期间不会改变局部变量表的大小。

  2. 所有线程共享的数据区
    java队、方法区

对象的引用

java中引用分为:强引用、软引用、弱引用、虚引用(幽灵引用或幻影引用),这4种引用强度逐渐减弱。

  • 强引用
    在程序代码中正常的类似于 “Person p = new Person()“这类的引用;垃圾回收器不会回收掉被强引用的对象
  • 软引用
    有用但非必须的对象,jdk中提供了SoftReference类来实现软引用
    系统在发生内存溢出异常时,会把软引用对象进行回收
  • 弱引用
    非必须的对象,jdk中提供了WeakReference类来实现弱引用,比软引用弱一些,垃圾回收不论内存是否不足都会回收只被弱引用关联的对象
  • 虚引用
    对被引用对象的生存时间不影响;
    无法通过虚引用来取得一个对象实例;
    为一个对象设置虚引用关联的唯一目的就是能在这个对象被收集器回收时收到一个系统通知;
    jdk提供PhantomReference类来实现虚引用

GC算法

标记-清除算法(Mark-Sweep)

  1. 标记出所有需要回收的对象,在标记完成后统一回收所有被标记的对象
    该算法的缺点:
  • 效率问题
    标记和清除两个过程的效率都不高
  • 空间问题
    标记清除后会产生大量不连续的内存碎片,空间碎片太多可能会导致以后在程序运行过程中需要分配较大对象时,无法找到足够的连续内存而不得不提前触发另一次垃圾回收动作

复制算法(Copying)

  1. 将可用内存按容量划分为大小相等的两块,每次只是用其中之一
  2. 当这一块的内存用完了,就将还存活的对象复制到另外一块上面,然后再把已使用过的内存空间一次清理掉
    该算法的优缺点:
  • 优点:
    这样使得每次都是对整个半区进行内存回收,内存分配时也就不用考虑内存碎片等复杂情况,只要移动堆顶指针,按顺序分配内存即可,实现简单,运行高效
  • 缺点:
  1. 实际可用内存只有原来的一半
  2. 复制算法在对象存活率比较高的情况下需要对较多的对象进行复制操作,效率会变低

现在的商业虚拟机都使用这种算法来回收新生代。
将内存分为一块较大的Eden空间和两块较小的Survivor空间,每次使用Eden和其中的一块Survivor空间,当回收时,将Eden和Survivor中还存活着的对象一次性复制到另外一块Survivor空间上,最后清理掉Eden和刚刚使用过的Survivor空间。
HotSpot虚拟机默认Eden和Survivor的大小比例是8:1,浪费10%

当Survivor空间不够用的时候,需要依赖其他的内存(这里指老年代)进行分配担保

标记整理算法(Mark-Compact)

  1. 标记出需要清除的对象
  2. 让所有存活的对象都向一端移动,然后直接清理掉端边界以外的内存

分代收集算法(Generational Collection)

  1. 根据对象存活周期将内存划分为几块
  2. 一般是把java堆分为新生代和老年代,这样就可用根据各个年代的特点采用最恰当的收集算法
  3. 在新生代中,每次垃圾回收时都会发现有大量的对象死去,只有少量存活,所以适合复制算法,只需要付出少量存活对象的复制成本就可用完成收集
  4. 老年代中因为对象存活率高、没有额外空间对它进行分配担保,就必须使用“标记-清理”或者“标记-整理”算法来进行回收