jvm 内存分配与垃圾回收

来源:互联网 发布:pk10软件下载 编辑:程序博客网 时间:2024/05/17 04:20

一般粗略的可以把内存的分配分为栈区和堆区,但是对于理解垃圾回收和分配还要分得细一点。如下图:
这里写图片描述
分为堆和方法区,虚拟机栈和本地方法栈,程序计数器。
前两个是多线程共享的,后面三个是每个线程单独的。

方法区

保存的是加载的类信息,常量(有常量池),静态变量等与实例无关与类有关的信息。里面还包括运行常量池,会在类加载时,把class文件常量池的字面量,符号引用等装进运行常量池。使用String的intern()方法时也会把常量装入运行常量池。

就是存放对象实例的地方,也是垃圾回收器管理的主要区域。它的详细划分将在后面说。

java虚拟机栈

当进入一个方法的时候就会创建一个栈帧,里面存放局部变量表,操作数栈,动态链接(不知道是什么),方法出口信息。每个方法的调用就是栈帧在虚拟机栈中出栈和入栈的过程。

本地方法栈

和虚拟机栈类似,不同的是它存放的是调用native()方法的存储的数据。

程序计数器

就是记录代码执行的地址的,代码执行的行号指示器。

对象创建

当虚拟机遇到new指令时,检查指令后面的参数能否定位到常量池中的一个类的符号引用,并检查符号引用的类是否已经加载,解析,初始化过。

内存分配的方式

如果内存是连续的,则计算创建的对象的大小,并移动指针同样大小的距离进行分配,这种方式叫“指针碰撞”。
如果内存不连续,用”空闲列表“记录没有使用的空间。

解决并发分配内存问题

一种是使用CSA和失败重试的方式保证指针更新的原子性(不知道CSA是什么只知道可以处理并发)
第二种是,每个线程划分自己的TLAB(本地线程分配缓冲),每个线程的指针在自己的缓冲区上移动。

句柄和直接指针

句柄就是指向指针的指针,直接指针就直接指向的对象实例的堆地址
这里写图片描述

判断对象的死亡

引用计数法

给对象添加一个引用计数器,当有一个地方引用它的时候,+1。引用失效时,-1。
问题,当两个对象相互引用时,对象已经无法访问了,但是因为计数器不为0,GC无法回收他们。

ObjA obja = new ObjA();ObjB objb = new ObjB();obja.instance = objb;objb.instance = obja;

似乎表难理解什么时候有引用,什么时候失效.

可达性分析算法

以GC Roots对象为起点,搜索它的引用链。如果存在引用该对象则可达。没有则不可达。
可作为GC Roots的对象

  • 虚拟机栈中的局部变量表中的变量
  • 本地方法栈中引用的变量
  • 方法区常量引用的对象
  • 方法区静态变量引用的对象

垃圾收集算法

分代收集算法
只是定义了新生代和老年代使用不同的收集算法
新生代因为对象生存周期短使用复制算法,老年代则使用标记-清除和标记整理算法

标记-清除

每个对象不可达后会被标记,并筛选。
筛选的标准是,如果对象没有执行过finalize方法或覆盖了finalize方法,则把它放到一个队列,会有一个优先级比较低的线程执行对象的finalize方法。在执行finalize方法时,可以使对象重新被引用到。这样就不会被回收。这是对象拯救自己的最后的机会。但是可能因为执行finalize线程的优先级比较低,所以作者建议不要依赖这个方法。
如果标记后没有被筛选到队列,那么真的就会被回收。另外finalise方法只能执行一次。

复制算法

把堆分为eden space,form survival,to survival。对象分配在eden,每次gc后,将eden和survival存活的对象放到另一个survival。当存活的对象大于survival时,就需要依赖老年代(老年代使用其他的算法回收对象)。

标记-整理

让存活的对象向一段移动,直接清除边界外的内存。

内存分配

对象优先进入eden

大对象直接进入老年代

经历过一定次数的对象会进入老生代

动态对象年龄判定

空间分配担保

如果老年代的空间大于,新生代的总对象的大小,则MinorGC是安全的。否则根据HandlePromissionFailure设置是否冒险。冒险是指,根据上一次虚拟机会检查历次放入晋升到老年代的对象的平均大小,如果小于则执行MinorGC否则FullGC。

0 0
原创粉丝点击