深入理解Java内存模型

来源:互联网 发布:站群软件 编辑:程序博客网 时间:2024/05/16 12:05

            

      Java虚拟机的运行时内存按照类型可分为5部分:Java方法区、Java栈、Native方法区、Java堆和程序计数器。 其中栈和程序计数器不能跨线程访问。

程序计数器:是一块较小的内存空间,它可以看作是当前线程所执行的字节码的行号指示器。每条线程都需要有一个独立的程序计数器,各条线程之间计数器互不影响,独立存储,而且永远不会发生Out of Memory问题,其它四种内存区域都可能出现OOM现象。


Java虚拟机栈(Java Virtual Machine Stacks)也是线程私有的,它的生命周期与线程相同, 跟C语言类似不使用new关键字的变量存放在栈里。在栈里可能出现StackOverflowException或者OOM异常。


本地方法栈: 就是Native层用C语言编写的栈(没调用new),其它跟Java栈类似。


Java堆:所有的java队形实例、数组都在堆上new出来,是垃圾收集器(Garbage Collection)管理的主要区域。Java堆可以处在物理不连续的内存空间中, 但逻辑上要是连续的。在实现时,既可以实现成固定大小的,也可以是可扩展的,不过当前主流的虚拟机都是按照可扩展来实现的(通过-Xmx和-Xms控制)。在日常编程中遇到的OOM异常大都发生在Java堆。


方法区(Method Area)与Java堆一样,是各个线程共享的内存区域,它用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。


上面是JVM的内存模型, 那么一个Java对象在内存里分为几部分呢?

答案:对象在内存中存储的布局可以分为3块区域:对象头(Header)、实例数据(Instance Data)和对齐填充(Padding)。


那么JVM是如何回收内存的呢? 


一、引用计数法: 当指向的Java对象的引用计数为0时, 在下次GC回收时会被释放。

考虑问题: 如果出现相互引用, 即A指向B,B指向A, 那么GC能回收吗?

   public static class ReferenceCountingGC{   public Object instance=null;   private final int  ONE_MB=1024*1024;   private byte[]bigSize=new byte[1*ONE_MB]; //只为了占空间   public static void testGC(){   ReferenceCountingGC objA = new ReferenceCountingGC();       ReferenceCountingGC objB = new ReferenceCountingGC();       objA.instance = objB;       objB.instance = objA;       objA = null;       objB = null;        //假设在这行发生GC,objA和objB是否能被回收?       long begin = Runtime.getRuntime().totalMemory();       System.out.println("befor:" + begin);       System.gc();       long after = Runtime.getRuntime().totalMemory();       System.out.println("after:" + after);       System.out.println("diff:" + (after-begin));  //判断到底释放了多少   }}
执行结果:

befor:16252928
after:16318464
diff:65536

     GC前后差了64K字节, 而objA和objB都大于1M字节, 现实没有被释放! 所以编程中切忌不要相互引用!!!

二、可达性分析算法

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

在Java语言中,可作为GC Roots的对象包括下面几种:虚拟机栈(栈帧中的本地变量表)中引用的对象;方法区中类静态属性引用的对象;方法区中常量引用的对象;本地方法栈中JNI(即一般说的Native方法)引用的对象。




    关于内存基础知识就说这么多, 下一篇准备写如何计算Java对象的大小。

0 0