一、Java内存分配



1. Java有几种存储区域?



寄存器



     --在CPU内部,开发人员不能通过代码来控制寄存器的分配,有编译器来管理。





    --在windows下,栈是向底地址扩展的数据结构,是一块连续的内存的区域,即栈顶的地址和栈的最大容量是系统预先定好的。 



     --优点:由系统自动分配,速度较快。



     --缺点:不够灵活,程序员无法控制。



     --存放基本数据类型、开发过程中就创建的对象(而不是运行过程中)。





     --是向高地址扩展的数据结构,是不连续的内存区域。



     --在堆中,没有堆栈指针,为此也就无法直接从处理器那边获得支持。



     --堆的好处是有很大的灵活性。如Java编译器不需要知道从堆里需要分配多少存储区域,也不必知道存储的数据在堆里会存活多长时间。



静态存储区域与常量存储区域



     --静态存储区用来存放static类型的变量



     --常量存储区用来存放常量类型(final)类型的值,一般在只读存储器中。



非RAM存储



     --如流对象,是要发送到另一台机器上。



     --持久化的对象,存放在磁盘上。



 



2. Java内存分配



     --基础数据类型直接在栈空间分配。



     --方法的形式参数,直接在栈空间分配,当方法调用完成后从栈空间回收。



     --引用数据类型,需要用new来创建,既在栈空间分配一个地址空间,又在堆空间分配对象的类变量。



     --方法的引用参数,在栈空间分配一个地址空间,并指向堆空间的对象区,当方法调用完后从栈空间回收。



     --局部变量new出来时,在栈空间和堆空间中分配空间,当局部变量生命周期结束后,栈空间立即被回收,堆空间区域等待GC回收。
     --方法调用时传入的literal参数,先在栈空间分配,在方法调用完成后从栈空间释放。



     --字符串常量在DATA区域分配,this在对空间分配



     --数组既在栈空间分配数组名称,又在堆空间分配数组实际的大小



 



3. Java内存模型



方法区、java栈、java堆。



方法区是静态分配的,编译器将变量绑定在某个存储位置上,而且这些绑定不会再运行时改变。



          常数池,源代码中的命名常量、string常量和static变量保存在方法区。



Java Stack是一个逻辑概念,特点是后进先出。一个栈的空间可能是连续的,也可能是不连续的。



          最典型的stack应用是方法的调用,java虚拟机每调用一次方法就创建一个方法帧(frame),退出时则对应的方法帧被弹出。栈中存储的数据也是运行时确定的。



Java堆分配 意味着以随意的顺序,在运行时进行存储空间分配和收回的内存管理模型。



          堆中存储的数据常常是大小、数量和生命期在编译时无法确定的。java对象的内存总是在heao中分配。



 



 



二、Java垃圾回收(GC)



1. JVM运行环境中垃圾对象的定义



     一个对象创建后被放置在JVM的堆内存中,当永远不再引用这个对象时,它将被JVM在堆内存中回收。或当对象在JVM运行空间中无法通过根集合(root set)到达时,这个对象就被称为垃圾对象。



 



2. 堆内存



     在JVM启动时被创建;堆内存中所存储的对象可以被JVM自动回收,不能通过其他外部手段回收。



     堆内存可分为两个区域:新对象区和老对象区



     --新对象区可分为三个小区:Eden区、From区、To区



 



3. JVM中对象的生命周期



创建阶段



     --为对象分配存储空间



     --开始构造对象



     --递归调用其超类的构造方法



     --进行对象实例初始化与变量初始化



     --执行构造方法体



     



     应用阶段



     --特征:系统至少维护着对象的一个强引用;所有对该对象引用强引用(除非显示声明为其他引用)



     



不可视阶段



     --如果一个对象已使用完,并且在其可视区域不再使用,应该主动将其设置为null,即obj=null;这样可以帮助JVM及时发现这个垃圾对象,并且可以及时地回收该对象所占用的系统资源。



          



4. Java中的析构方法finalize



     finalize()方法常称之为终止器



     protected void finalize(){



           // finalization code here



     }



     对象即将被销毁时,有时需要做一些善后工作,可以把这些操作写在finalize()方法里。



     Java终止器却是在对象被销毁时调用。一旦垃圾收集器准备好释放无用对象占用的存储空间,它首先调用那些对象的finalize()方法,然后才真正回收对象的内存。而被丢弃的对象何时被销毁,应用是无法获知的。大多数场合,被丢弃对象在应用终止或仍未销毁。到程序结束的时候,并非所有收尾模块都会得到调用。



 



5. 应用能干预垃圾回收吗?



     在应用代码里控制JVM的垃圾回收运转是不可能的事。



     对垃圾回收有两个途径。第一个就是将指向某对象的所有引用变量全部移走。这就相当于向JVM发了一个消息:这个对象不要了。第二个是调用库方法System.gc()。第一个是一个告知,而调用System.gc()也仅仅是一个请求。JVM接收这个消息后,并不是立即做垃圾回收,而只是对几个垃圾回收算法做了加权,使垃圾回收操作容易发生,或提早发生,或回收较多而已。



     希望JVM及时回收垃圾,是一种需求。其实,还有相反的一种需要:在某段时间内最好不要回收垃圾。要求运行速度最快的实时系统,特别是嵌入式系统,往往希望如此。



     Java的垃圾回收机制是为所有java应用进程服务的。因此,任何一个进程都不能命令垃圾回收机制做什么、怎么做、做多少。



 



6. 垃圾回收算法



*引用计数



     该算法在java虚拟机没被使用过,主要是循环引用问题,因为计数并不记录谁指向他,无法发现这些交互自引用对象。



     --怎么计数?



          当引用连接到对象时,对象计数加1



          当引用离开作用域或被置为null时减1



     --怎么回收?



          遍历对象列表,计数为0就释放



     --有什么问题?



          循环引用问题



 



*标记算法



标记算法的思想是从堆栈和静态存储区的对象开始,遍历所有引用,标记活的对象。



对于标记后有两种处理方式:



(1)停止-复制



     --所谓停止,就是停止在运行的程序,进行垃圾回收



     --所谓复制,就是将活的对象复制到另一个堆上,以使内存更紧凑



     --优点在于,当大块内存释放时,有利于整个内存的重新分配



     --有什么问题?



          ->停止,干扰程序的正常运行



          ->复制,明显耗费大量时间



          ->如果程序比较稳定,垃圾比较少,那么每次重新复制量是非常大的,非常不合算



(2)标记-清除



     --也就是将标记为非活的对象释放,也必须暂停程序运行



     --优点就是在程序比较稳定,垃圾比较少的时候,速度比较快



     --有什么问题?



          很显然停止程序运行是一个问题,只清楚也会造成很多内存碎片



     --为什么这两个算法都要暂停程序运行?



          如果不暂停,刚才的标记会被运行的程序弄乱