深入理解JVM之内存管理

来源:互联网 发布:知乎 南京装修工作室 编辑:程序博客网 时间:2024/06/04 23:42

1 内存空间:分为方法区,堆,本地方法栈,PC寄存器及JVM方法栈

    1)方法区存放了要加载的类的信息,类的静态变量,final类型的常量,Field信息,类中方法信息

         (对应持久代:permanet Generation,默认最小值是16M,最大值是64M,可以通过-XX:PermSize及-XX:MaxPermSize来指定最小值和最大值)

    2)堆存放对象实例和数据值(由-Xms和-Xmx控制,在实际系统中通常设置成一样)

          1))新生代:大多数新建的对象都从新生代分配内存,新生代由Eden space和两块大小相同的Survivor Space组成(通过-Xmn指定新生代的大小,通过-XX:SurvivorRatio=Eden space/Survivor Space配置Eden space 和Survivor Space所占的比例,默认为8,当-XX:InitialSurvivorRatio也存在时以-XX:InitialSurvivorRatio为准)

          2))旧生代:存放在新生代中经过多次垃圾回收仍然存活的对象,如缓存对象;新建的对象也有可能在旧生代上分配(一种是大对象,另一种是大的数组对象,且数据对象无引用外部对象,-XX:PretenureSizeThreshold=1024控制,表示对象大于1k时直接在旧生代上分配)。

    3)本地方法栈:支持native方法的执行

    4)PC寄存器和JVM方法栈:PC寄存器占用的可能为CPU寄存器或操作系统内存,JVM方法栈(由-Xss控制)占用的为操作系统内存,JVM方法栈为线程私有。

2 内存分配

   1)java对象使用的内存主要从堆上分配。sun jdk为了提上内存分配的效率,在新生代的Eden space上分配出来一块TLAB(thread local allocation buffer)空间(通过-XX:TLABWasteTargetPercent指定TLAB占Eden space空间的百分比,默认1%,增加JVM  -XX:+PrintTLAB参数可以查看TLAB空间使用情况)

3 内存回收

   1)垃圾回收收集器:引用计数收集器和跟踪收集器

         1))引用计数收集器采用分散的管理方式,sun jdk没采用这种方式

         2 ))跟踪收集器采用集中的管理方式,主要有复制(Copying),标记-清除(Mark-Sweep),标记-压缩(Mark-Compact)三种实现算法

               不同代的对象,sun jdk采用不同的跟踪收集方式

              新生代采用Copying算法,Sun JDK采用串行GC(Serial Copying),并行回收GC(Parallel Scavenge),并行GC(ParNew)的方式进行对新生代对象的回收。新生代Survivor Space两块相同的区域,一块作为复制的目标空间(俗称To Space),另一块被清空(俗称From Space),对新生代对象占用的内存进行的GC通常称为Minor GC,通常存活的对象在Minor GC后不直接进入到旧生代,只有经历过几次Minor GC后仍然存活的才进入旧生代,具体几次通过-XX:MaxtenuringThreshold来控制(串行GC和并行GC),并行回收GC则则有Hotspot根据运行状况来决定。当To Space空间满了,剩下的存活对象直接转入旧生代。

             串行GC在整个扫描和复制过程中均采用单线程的方式,-XX:+UseSerialGC的方式指定使用串行GC,新生代分配内存采用的为空闲指针(bump-the-pointer)的方式,指针保持最后一个分配的对象在新生代内存区间的位置,当有新的对象要分配内存时,只需检查剩余的空间是否够存放新的对象,够则更新指针,并创建对象,不够则触发Minor GC。Sun JDK认为以下对象为根集合对象:当前运行线程的桟上引用的对象,常量及静态变量,传到本地方法中,还没有被本地方法释放的对象引用。

            为了避免在扫描过程中引用关系变化,Sun Jdk采用了暂停应用的方式,Sun Jdk在编译代码时为每段方法注入了SafePoint,通常SafePoint位于方法中循环的结束点及方法执行完毕的点,在暂停应用时需要等待所有的用户线程进入SafePoint,在用户线程进入SafePoint后,如果发现此时要执行Minor GC,则将其内存页设置为不可读的状态,从而实现暂停用户线程的执行。

            并行回收GC:采用Copying算法,在扫描和复制过程中均采用多线程的方式,当Eden Space空间不足时直接在旧生代上分配,-XX:ParallelGCThreads=4指定线程数

           -----------------------------------------

           旧生代和持久可用的GC:jdk提供了串行,并行,并发三种GC方式

          1)串行:基于Mark-Swap-Compact实现(Mark-Swap,Mark-Compact两者的改进),和新生代的串行GC相同,采用单线程方式,通过-XX:+PrintGCApplicationStoppedTime查看GC造成的应用暂停时间

          2)并行:采用Mark-Compact实现,内存分配上和串行方式相同,几个线程则把旧生代划分为几个区域

          3)并发(Concurrent Mark-Sweep CMS):采用Mark-Swap方式实现,采用free-list的方式记录旧生代那些空间是空闲的,为避免对free-list的竞争,引入了Mutual exclusion locks,以分配内存为优先。由于并发进行,这样势必和应用抢占CPU资源,为降低这种现象,CMS引用了i-CMS模式(CMS仅启用一个处理器线程来并发扫描,标记,清除。并且该线程使用一小段时间后会将CPU使用权让出来,分多次多段的进行扫描,标记,清除。适用于CPU少且内存分配不频繁的应用),CMS回收方式容易引起内存碎片,为提高内存使用率,CMS提供了-XX:UseCMSCompactAtFullCollection(进行时需要暂停整个应用)启动参数,使JVM进行CMS方式进行FULL GC后进行内存压缩。CMS容易引起“浮动垃圾(本该本次回收放到下次回收),默认情况下CMS GC并不开启,可以增加-XX:UseConcMarkSweepGC强制启用CMS进行旧生代对象的GC,开启回收线程数=(并行GC线程数 + 3)/ 4, 也可以通过-XX:ParallelCMSThread=10 来指定。CMS GC触发的条件是旧生代已使用的空间达到设定的CMSInitatingOccupancyFraction的百分比(默认68%),另一种是JVM自动触发,也可以设置CMSInitatingOccupancyOnly=true禁止JVM自动触发。

                  CMS执行扫描,着色,清除步骤:

                         1)第一次标记(initial Marking):需暂停整个应用

                         2)并发标记(Concurrent Marking):恢复应用的线程,在进行并发标记时Minor GC可能并行进行,这时很可能引起旧生代的引用关系改变,CMS为避免这样的并发现象,提供了Mod Union Table来进行记录。

                         3)重新标记(Final Marking(remark)):需暂停整个应用

                         4)并发收集(Concurrent Sweeping):完成重新标记后,恢复所有应用的线程。

        FULL GC:除CMS GC外旧生代和持久代触发GC时,其实是对新生代,旧生代和持久代都GC,因此通成为FULL GC(当新生代采用PS GC时,可以通过配置-XX:+ScavengeBeforeFullGC来禁止对新生代进行GC)  

        触发FULL GC的四种情况:

             1)旧生代空间不足

             2)Permanet Generation空间满

             3)CMS GC时出现promotion failed(minor GC时Survivor Space放不下,对象只能放在旧生代,旧生代也放不下造成的)和concurrent  mode failed时可能触发

             4) 统计得到Minor GC晋升到旧生代的平均大小大于旧生代的剩余空间

            另一种情况,对于RMI来进行RPC或管理的Sun JDK应用而言,默认情况一个小时触发一次FULL GC(可以通过-java -Dsun.rmi.dgc.client.gcInterval=36000来设置FULL GC执行时间间隔)    

总结:

    在client和server模式下的默认选择并不相同,现总结如下:

                                            新生代GC方式                         旧生代和持久代GC方式

               client                      串行GC                                        串行GC

               server                   并行回收GC                                  并行GC(JDK 5.0Update 6以后)                    

    sun JDK GC的组合方式                                新生代GC方式                                           旧生代和持久代GC方式

     -XX:+UseSerialGC                                              串行GC                                                             串行GC 

     -XX:+UseParallelGC                                          并行回收GC                                                     并行GC

     -XX:+UseConcMarkSweepGC                         并行GC                                                             并发GC(当出现concurrent Mode failure 时采用串行GC)

     -XX:+UseParNewGC                                          并行GC                                                             串行GC

     -XX:+UseParallelOldGC                                    并行回收GC                                                     并行GC

     -XX:+UseConcMarkSweepGC,-XX:-UseParNewGC      串行GC                                          并发GC(当出现Concurrent Mode failure 或promotion failed时采用串行GC)       

        sun JDK提供了两种简单的方式帮助选择GC

              1)吞吐量优先

              2)暂停时间优先(默认不启用改策略)

    Garbage First  在jdk1.6 update14以上及1.7中使用(实际项目应用不太多)

    Real-Time JDK:为了满足实时领域系统使用java的需求,java推出了Real-Time版的规范

           在以下方面进行了优化:

            1)新的内存管机制:提供了两种内存区域,以下两种内存区域均不受GC管理

                        1))Immortal内存区域:永久保存的对象,应用结束时释放

                        2))Scoped内存区域:临时对象,scope退出是对象释放

            2)允许java应用直接访问物理内存:可以直接将对象直接放到物理内存,而非java heap中。

4 补充:

     1)字节码是由执行引擎来执行的,不同的java虚拟机中,执行引擎可能实现的不同,大致有以下执行引擎

          1))最简单的是一次性解释字节码

          2))及时编译器(just-in-time compiler),更消耗内存,第一次被执行的字节码会被编译成本地机器代码,本地机器代码会被缓存,当方法以后调用的 时候可以重用。

          3))自适应优化器,虚拟机开始的时候解释字节码,并会监视运行中程序的活动,并记录下使用最频繁的代码段。程序运行的时候,虚拟机只把频繁使用的代码编译成本地代码,不是很频繁的继续保留为字节码。

           

0 0
原创粉丝点击