你的位置:首页 > 软件开发 > Java > Java内存模型与垃圾回收

Java内存模型与垃圾回收

发布时间:2016-05-14 23:00:13
1、Java内存模型  Java虚拟机在执行程序时把它管理的内存分为若干数据区域,这些数据区域分布情况如下图所示:程序计数器:一块较小内存区域,指向当前所执行的字节码。如果线程正在执行一个Java方法,这个计数器记录正在执行的虚拟机字节码指令的地址,如果执行的是Native方法, ...

Java内存模型与垃圾回收

 

1、Java内存模型

  Java虚拟机在执行程序时把它管理的内存分为若干数据区域,这些数据区域分布情况如下图所示:

Java内存模型与垃圾回收

  • 程序计数器:一块较小内存区域,指向当前所执行的字节码。如果线程正在执行一个Java方法,这个计数器记录正在执行的虚拟机字节码指令的地址,如果执行的是Native方法,这个计算器值为空。
  • Java虚拟机栈:线程私有的,其生命周期和线程一致,每个方法执行时都会创建一个栈帧用于存储局部变量表、操作数栈、动态链接、方法出口等信息。
  • 本地方法栈:与虚拟机栈功能类似,只不过虚拟机栈为虚拟机执行Java方法服务,而本地方法栈则为使用到的Native方法服务。
  •  Java堆:是虚拟机管理内存中最大的一块,被所有线程共享,该区域用于存放对象实例,几乎所有的对象都在该区域分配。Java堆是内存回收的主要区域,从内存回收角度看,由于现在的收集器大都采用分代收集算法,所以Java堆还可以细分为:新生代和老年代,再细分一点的话可以分为Eden空间、From Survivor空间、To Survivor空间等。根据Java虚拟机规范规定,Java堆可以处于物理上不连续的空间,只要逻辑上是连续的就行。
  • 方法区:与Java一样,是各个线程所共享的,用于存储已被虚拟机加载类信息、常亮、静态变量、即时编译器编译后的代码等数据。
  • 运行时常量池,运行时常量池是方法区的一部分,Class文件中除了有类的版本、字段、方法、接口等描述信息外,还有一项信息是常量池,用于存放编译期生成的各种字面量和符号引用。运行期间可以将新的常量放入常量池中,用得比较多的就是String类的intern()方法,当一个String实例调用intern时,Java查找常量池中是否有相同的Unicode的字符串常量,若有,则返回其引用;若没有,则在常量池中增加一个Unicode等于该实例字符串并返回它的引用。

2、垃圾对象如何确定

  Java堆中存放着几所所有的对象实例,垃圾收集器在对堆进行回收前,首先需要确定哪些对象还"活着",哪些已经"死亡",也就是不会被任何途径使用的对象。

引用计数法

  引用计数法实现简单,效率较高,在大部分情况下是一个不错的算法。其原理是:给对象添加一个引用计数器,每当有一个地方引用该对象时,计数器加1,当引用失效时,计数器减1,当计数器值为0时表示该对象不再被使用。需要注意的是:引用计数法很难解决对象之间相互循环引用的问题,主流Java虚拟机没有选用引用计数法来管理内存。

可达性分析算法

  这个算法的基本思路就是通过一系列的称为“GC Roots”的对象作为起始点,从这些节点开始向下搜索,搜索所走过的路径称为引用链(Reference Chain),当一个对象到GC Roots没有任何引用链相连(用图论的话来说,就是从GC Roots到这个对象不可达)时,则证明此对象是不可用的。如图所示,对象object 5、object 6、object 7虽然互相有关联,但是它们到GC Roots是不可达的,所以它们将会被判定为是可回收的对象。

Java内存模型与垃圾回收 

在Java语言中,可作为GC Roots的对象包括下面几种:

  • 虚拟机栈(栈帧中的本地变量表)中引用的对象。
  • 方法区中类静态属性引用的对象。
  • 方法区中常量引用的对象。
  • 本地方法栈中JNI(即一般说的Native方法)引用的对象。

  现在问题来了,可达性分析算**不会出现对象间循环引用问题呢?答案是肯定的,那就是不会出现对象间循环引用问题。GC Root在对象图之外,是特别定义的“起点”,不可能被对象图内的对象所引用。具体讲解参见:http://www.zhihu.com/question/29218534。

对象生存还是死亡(To Die Or Not To Die)

  即使在可达性分析算法中不可达的对象,也并非是“非死不可”的,这时候它们暂时处于“缓刑”阶段,要真正宣告一个对象死亡,至少要经历两次标记过程:如果对象在进行可达性分析后发现没有与GC Roots相连接的引用链,那它将会被第一次标记并且进行一次筛选,筛选的条件是此对象是否有必要执行finalize()方法。当对象没有覆盖finalize()方法,或者finalize()方法已经被虚拟机调用过,虚拟机将这两种情况都视为“没有必要执行”。程序中可以通过覆盖finalize()来一场"惊心动魄"的自我拯救过程,但是,这只有一次机会呦。

/**  * 此代码演示了两点:  * 1.对象可以在被GC时自我拯救。  * 2.这种自救的机会只有一次,因为一个对象的finalize()方法最多只会被系统自动调用一次  * @author zzm  */ public class FinalizeEscapeGC {   public static FinalizeEscapeGC SAVE_HOOK = null;   public void isAlive() {   System.out.println("yes, i am still alive :)");  }   @Override  protected void finalize() throws Throwable {   super.finalize();   System.out.println("finalize mehtod executed!");   FinalizeEscapeGC.SAVE_HOOK = this;  }   public static void main(String[] args) throws Throwable {   SAVE_HOOK = new FinalizeEscapeGC();   //对象第一次成功拯救自己   SAVE_HOOK = null;   System.gc();   //因为finalize方法优先级很低,所以暂停0.5秒以等待它   Thread.sleep(500);   if (SAVE_HOOK != null) {   SAVE_HOOK.isAlive();   } else {   System.out.println("no, i am dead :(");   }   //下面这段代码与上面的完全相同,但是这次自救却失败了   SAVE_HOOK = null;   System.gc();   //因为finalize方法优先级很低,所以暂停0.5秒以等待它   Thread.sleep(500);   if (SAVE_HOOK != null) {   SAVE_HOOK.isAlive();   } else {   System.out.println("no, i am dead :(");   }  } } 

原标题:Java内存模型与垃圾回收

关键词:JAVA

*特别声明:以上内容来自于网络收集,著作权属原作者所有,如有侵权,请联系我们: admin#shaoqun.com (#换成@)。