关于java内存的学习

 一 jvm 的内存模型分为静态存储区、栈存储区和堆存储区。

1 静态存储区

  内存在程序编译的时候就已经分配好了,这块内存在程序的整个运行期间都存在。比如:static、全局变量等。

2 栈存储区

  在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数存储结束后这些存储单元自动被释放。栈内存分配运算内置于处理器的指令集中、效率很高。但是分配的内存容量非常有限。

3 堆存储区

  动态内存分配。在c和c++中运行程序时用malloc或new申请任意大小的内存,我们需要自己决定自己在何时何地使用free和delete来释放内存。

 

上面介绍的是jvm的内存模型,而jvm在程序启动的过程中会把内存划分成不同的更具体的内存。

方法区:也称“永久代”、“非堆”,它用于存储虚拟机加载的类信息、常量、静态变量、是各个线程共享的内存区域。默认最小值为16MB,最大值为64MB,可以通过-XX:PermSize 和 -XX:MaxPermSize 参数限制方法区的大小。

虚拟机栈:描述的是java 方法执行的内存模型:每个方法被执行的时候 都会创建一个“栈帧”用于存储局部变量表(包括参数)、操作栈、方法出口等信息。每个方法被调用到执行完的过程,就对应着一个栈帧在虚拟机栈中从入栈到出栈的过程。声明周期与线程相同,是线程私有的。

本地方法栈:与虚拟机栈基本类似,区别在于虚拟机栈为虚拟机执行的java方法服务,而本地方法栈则是为Native方法服务。

堆:也叫做java 堆、GC堆是java虚拟机所管理的内存中最大的一块内存区域,也是被各个线程共享的内存区域,在JVM启动时创建。该内存区域存放了对象实例及数组(所有new的对象)。其大小通过-Xms(最小值)和-Xmx(最大值)参数设置,-Xms为JVM启动时申请的最小内存,默认为操作系统物理内存的1/64但小于1G,-Xmx为JVM可申请的最大内存,默认为物理内存的1/4但小于1G,默认当空余堆内存小于40%时,JVM会增大Heap到-Xmx指定的大小,可通过-XX:MinHeapFreeRation=来指定这个比列;当空余堆内存大于70%时,JVM会减小heap的大小到-Xms指定的大小,可通过XX:MaxHeapFreeRation=来指定这个比列,对于运行系统,为避免在运行时频繁调整Heap的大小,通常-Xms与-Xmx的值设成一样。

程序计数器:它的作用是当前线程所执行的字节码的行号指示器,在虚拟机的模型里,字节码解释器工作时就是通过改变这个计数器的值来选取下一条需要执行的字节码指令,分支、循环、异常处理、线程恢复等基础功能都需要依赖计数器完成。

了解了jvm对内存的详细划分不难发现java的内存回收机制主要是针对堆进行回收的。

二 jvm的GC机制

  jvm把堆分为新生代,老年代和持久代,见下图:

 

  新生代

    1 所有新生的对象首先都是放在新生代的。新生代的目标就是尽可能快速的收集掉那些生命周期短的对象。

    2 新生代的内存按照8:1:1的比例分为一个eden区和两个survivor(survivor0和survivor1)区。一个eden区和两个survivor区(一般而言)。大部分对象是在eden区生成的,回收时把eden区中的存活对象放入到survivor0中,然后清空eden区。如果survivor中的对象过多时造成survivor0满了  这是jvm会把eden和survivor0中存活的对象放入到survivor1中,同时清空eden和survivor0,然后将survivor0和survivor1进行交换,如此往复。这样就可以把eden和survivor0中的死亡对象都清空了。

    3 如果存活的对象加起来的容量大于一个survivor区的容量时,也就时说此时jvm已经不能进行上面2中的步骤(浅GC)了,那么就将存活的对象放入到老年代中。如果老年代也满了就会进行一次FullGC,也就是新生代和老年代都进行回收。

    4.新生代发生的GC也叫做Minor GC,MinorGC发生频率比较高(不一定等Eden区满了才触发)

  年老代

    1 在新生代中经历过n次垃圾回收后仍然存活的对象,就会被放到年老代中,可以认为年老代都是一些生命周期比较长的的对象。

    2 年老代的内存比新生代的内存大得很多,当年老代内存满时会触发FullGC,Full GC发生频率比较低,老年代的对象存活时间比较长,存活率标记高。

  持久代

    用于存放静态文件,如Java类、方法等。持久代对垃圾回收没有显著影响,但是有些应用可能动态生成或者调用一些class,例如Hibernate 等,在这种时候需要设置一个比较大的持久代空间来存放这些运行过程中新增的类。

三 jvm的GC中用到的一些算法

  在java的GC中jvm最关注的便是怎么判断一个对象是否存活,所以在jvm中使用了一些算法来进行判断。

  1 引用计数法

    这个方法顾名思义,就是每个对象都有一个引用计数,如果多一个引用就加一,反之减一,当计数为零时,这个对象就会被回收。

    这个算法有个很明显的缺点,如果两个对象互相引用,那么这两个对象都不可能被清除。

  2 标记清除算法

    这个算法是在根算法的基础上的,jvm找一个对象作为GC的root,从这个root对象寻找对应的引用节点,找到引用节点后,继续寻找这个节点的引用节点,当所有的引用节点寻找完毕后,其他没有被找到的节点就是无用的节点。

    java中可以作为root的对象有:虚拟机栈中引用的对象(本地变量表),方法区中静态属性引用的对象, 方法区中常量引用的对象,本地方法栈中引用的对象(Native对象)

    这个算法的缺点是:会产生很多零碎的不连续的内存碎片。

  3 标记清理算法

    这个算法是和标记清除算法差不多,只是在清除时会把存活的对象向前面内存中移,避免产生零碎不连续的内存片段,但成本较高。

  4 copying算法

    它把内存分为一个对象面和多个空闲面,程序从对象面为对象分配空间,如果对象面满了则从根集中进行扫描活动对象,把活动对象复制到空闲面,然后把对象面清除,这样对象面变成了空闲面,空闲面变成了对象面。

  5 generation算法

    分代的垃圾回收策略,是基于这样一个事实:不同的对象的生命周期是不一样的。因此,不同生命周期的对象可以采取不同的回收算法,以便提高回收效率。

    这个也就是现在jvm用的回收算法。

posted @ 2018-04-17 21:38  小小小怪兽  阅读(138)  评论(0)    收藏  举报