java 内存模型

图后续补

  1. 计数器:指令技术器,主要用于记录指令的行号。作用就是用于循环,跳转,进入分支代码等都要依赖这个计数器来实现。这块区域是唯一不会发生内存溢出都区域。每个线程都有单独的指令计数器。
  2. 虚拟机栈:每个线程都有自己的虚拟机栈,虚拟机栈是有栈帧构成的,每个栈帧包含着临时变量表、动态链接、计数器,方法出口。临时变量表里面存放着数据类型,包括值类型和引用类型。这个区域容易出现stackoverflow 和outofmemory 异常。
  3. 本地方法区:就是处理Java中的本地方法,比如object对象中的native方法,wait以及hashcode。
  4. 堆。共享区域,实例对象所在的区域,是jvm内存回收重点的优化对象区域。可以用 -Xmx和-Xms 参数来跳转jvm 内存大小.该区域很容易出现outofmemory异常。
  5. 方法区。是一个共享区域,主要是用于存放静态变量和常量的区域,方法区属于持久代。目前jvm 对该区域也没有很好的内存回收的优化策略。该区域还包含运行时常量池。大部分常量在编译时就能确定,少部分常量是在运行时比如String的intern。方法区也可能会出现outofmemory的异常错误。

内存回收策略

  1. 引用计数法。简单的描述就是一个对象如果有人引用则计数器加1,如果没有少一个对象引用,则计数器减1。只回收计数器为0的对象。该方法最致命的缺点就是无法解决循环引用的问题,就是对象A引用对象B,对象B引用对象A。除此以外对象A和B不被其他对象所引用。
  2. 根搜索算法。设立各种根对象,当根对象到某一个对象不可达时,就说明该对象可以被回收。可以充当根节点的有以下四种:
    1. 虚拟机栈中的引用对象。
    2. 方法区中类静态变量的引用对象。
    3. 方法区中常用的引用对象。
    4. 本地方法栈中的JNI的引用对象。

根搜索算法主要是以下三种

  1. 标记-清除:从根节点开始往下遍历,如果能够被遍历到的节点打上标记。当全部遍历完成后,回收那些未被打上标记的对象。具体的做法就是初始化对象的标记状态为0,当jvm 暂停线程启用回收策略时。会从根节点开始遍历,被遍历到的对象会打上相应的标记1。当遍历完成后就开始回收标记为0的对象。然后再次遍历,将原来标记为1的对象状态还原成0。在内存回收的时候需要暂停线程的原因是,当A对象已经被打上相应可访达的标记1后。A对象创建了一个B对象。但是B对象错过了本轮的遍历,导致刚刚创建的B对象会被回收。所以在回收的时候必须要先暂停线程。这种算法的缺点就是效率低,需要遍历。而且会产生很多的内存碎片。
  2. 复制算法:就是将内存划分成两个50%的区域。一个为活动区,一个为空闲区。在程序运行期间将分配给对象的内存空间都在活动区。当启用内存回收策略的时候。将存活的对象从活动区复制到空闲区,并按照内存顺序,连续的存放在一起。然后修改原对象的地址到空闲区的新地址。将空闲区变成活动区,将原来的活动区变成了空闲区。然后把现在空闲区的内容全部回收。解决标记-清除算法里面的内存碎片的问题。但是由于要复制对象。本身也不是很高效。特别在存活率很高的情况下。会造成大量由于复制导致的性能损耗,而且是无用功。而且还要浪费50%的内存空间。
  3. 标记-整理:先从根节点开始遍历,能够遍历到的对象打上标记。整理所有存活的对象,按照内存地址依次排列在一起。回收队尾所有未标记的对象。缺点就是本身效率也不高,标记后还需要整理内存空减。相比复制算法提高了内存利用率。

分代回收算法

将内存里面的对象划分为新生代、老年代和永久代。永久代在方法区。将内存里面的空间按照1:2的方式划分三分之一的内存给新生代,划分三分之二的内存给老年代。其中新生代的内存结构又分成Eden和Survivor。Survivor 又分成 from 和to 两个空间。三者之间的比例是8:1:1。

由于新生代存活率比较低。所有新生代的回收策略采用的是复制法。Eden 用于分配新创建的对象,from区域用于存放上一次回收存活的对象。当发生回收的时候,会把Eden和from两个区域中存活的对象移动到空间区域。然后回收Eden和from区域的内存。当Survivor不够存放上一次内存回收存活对象时,这些对象就会被移动到老年代内存区域。

由于老年代的存活率比较高。所以老年代的回收策略是标记-整理或者标记-清除算法。

永久代中的内存也是需要回收的,否则也会出现OOM的问题。

内存回收器

Serial收集器:串行收集器,单线程回收。会造成stop the world 。一般用在客户端程序。

ParNew收集器:并行收集器,相比Serial。加了多线程的机制并行回收。也会出现stop the world。

Parallel收集器:采用复制算法的并行回收器。更加的注重系统吞吐量。吞吐量=程序运行时间/程序运行时间+垃圾回收时间。该收集器是jdk 1.7和jdk1.8新声代默认的回收器。

ParallelOld收集器: 采用标记-整理的算法的并行收集器,主要是用在老年代。

CMS收集器:CMS收集器主要是用在老年代回收。流程主要是初始标记、并发标记、重新标记、标记清除。CMS回收是会造成内存碎片的。在初始标记还有重新标记阶段会造成stop the world。

G1 收集器:并行并发、分代收集、空间整合(当大对象无法分配到一个连续的空间时,就会触发一次回收)、可预测的停顿时间。g1 的原理也是年轻代采用复制采用复制的算法。老年代采用标记-清除算法。但是g1还是可能会有内存碎片。

你可能感兴趣的:(java)