zoukankan      html  css  js  c++  java
  • 03_垃圾回收

    【简述】

    垃圾回收GC(Garbage Collection),GC中的垃圾,特指存于内存中不会再使用的对象,回收相当于清除垃圾。

    垃圾回收有很多种算法,如:引用计数法、标记压缩法、复制算法、分代分区思想。

    [ 引用计数法 ]

    是比较古老经典的垃圾收集算法,其核心就是对象在被其引用时计数器+1,而当引用失效时-1,这种方式有一个非常严重的问题:无法处理循环引用的情况,且每次进行操作比较浪费系统性能。

    [ 标记清除法 ]

    分为标记和清除两个阶段来处理内存中的对象。这种方式也有一个弊端:空间碎片问题,垃圾回收后的空间不是连续的,不连续的内存空间的工作效率要低于连续的内存空间。

    [ 复制算法 ]

    其核心思想就是将内存空间分为两块,每次只使用其中一块,在垃圾回收时,将正在使用的内存中的存留对象复制到未被使用的内存块中,之后去清除之前正在使用的内存块中的所有对象,反复去交换这两个内存的角色,完成垃圾收集。

    Java中新生代的from和to区就是使用这个算法。

    [ 标记压缩法 ]

    标记压缩法对标记清除法基础上做了优化,把存活的对象压缩到内存一段,然后进行垃圾清除。

    Java中老年代使用的就是标记压缩法。

    【 分代算法和分区算法 】 

    [ 分代算法 ]

    就是根据对象的特点把内存分成N块,然后根据每个内存的特点使用不同的算法。

    对于新生代和老年代来说,新生代回收的频率很高,但是每次回收耗时都很短。

    而老年代回收的频率很低,相对耗时会比较长,所以应该尽量减少老年代的GC。

    [ 分区算法 ]

    主要就是将整个内存分为N多个小的独立空间,每个小空间都可以独立使用,这样细粒度地控制一次回收多少个小空间和哪些小空间,而不是对整个空间进行GC,从而提升性能,并减少GC的停顿时间。

    【垃圾回收时的停顿现象】

    垃圾回收器的任务是识别和回收垃圾对象进行垃圾清理,为了让垃圾回收器高效地执行,大部分情况下,会要求系统进入一个停顿的状态,停顿的目的是终止所有的应用程序,只有这样系统才不会有新的垃圾产生,同时停顿保证了系统状态在某一瞬间的一致性,也有利于更好地标记垃圾对象。

    因此在垃圾回收的时,都会产生应用程序的停顿。

    【对象如何进入老年代】

     一般对象首次创建会被放在新生代的eden区域,如果没有GC介入,则对象不会离开eden区。

    那么对象怎么进入老年代呢?

    一般来讲,只要对象的年龄达到一定的大小后,就会自动离开新生代进入老年代,对象的年龄是由对象经历数次GC决定的,在新生代每次GC之后如果对象咩有被回收则年龄+1,虚拟机提供了一个参数来控制新生代对象的最大年龄,当超过这个年龄范围就会晋升到老年代。

    -XX:MaxTenuringThreshold,默认情况下是15。

    【初始的对象分配在eden区】

    package com.jvm.demo01;
    
    public class Demo05 {
    
        public static void main(String[] args) {
            //初始的对象在eden区
            //参数:-Xmx64M -Xms64M -XX:+PrintGCDetails
            for(int i=0; i< 5; i++){
                byte[] b = new byte[1024*1024];
            }
        }
    }

    【-Xmx64M -Xms64M -XX:+PrintGCDetails 运行结果】

    【测试进入老年代的对象】

    package com.jvm.demo01;
    
    import java.util.HashMap;
    import java.util.Map;
    
    public class Demo05 {
    
        public static void main(String[] args) {
    
            //测试进入老年代的对象
            //参数:-Xmx1024M -Xms1024M -XX:+UseSerialGC -XX:MaxTenuringThreshold=15 -XX:+PrintGCDetails 
            //每次分配1M内存,执行6000次
            for(int k = 0; k<20; k++) {                
                for(int j = 0; j<300; j++){
                    byte[] b = new byte[1024*1024]; 
                }
            }
        }
    }

    【运行结果】

    [ 总结 ]

    根据设置MaxTenuringThresgold参数,可以指定新生代对象经历多少次回收后进入老年代。

    另外,大对象(新生代eden区无法装入时,也会直接进入老年代)。JVM里有个参数可以设置对象的大小超过指定的大小之后,直接进入老年代(针对大对象)。

    -XX:PretenureSizeThreshold  

    【大对象直接进入老年代的例子】

    package com.jvm.demo01;
    
    import java.util.HashMap;
    import java.util.Map;
    
    public class Demo06 {
            
        public static void main(String[] args) {
            //-XX:PretenureSizeTheshold 可以直接指定进入老年代的对象大小 -XX:PretenureSizeThreshold=1024*1000  < 1024*1024  
            //-Xmx30m -Xms30m -XX:+UseSerialGC -XX:+PrintGCDetail -XX:PretenureSizeThreshold=1024000
            Map<Integer, byte[]> m = new HashMap<Integer, byte[]>();
            
            for(int i=0;i<5;i++){
                byte[] b =new byte[1024*1024]; //每次分配的对象大小都大于设定的1024*1000
                m.put(i, b);
            }
        }
        
    }

    【大对象直接进入老年代的运行结果】

    【-XX:PretenureSizeThreshold  设置的值较小(1024B),生成对象(1024)稍大,但没有存入老年代的例子】

    package com.jvm.demo01;
    
    import java.util.HashMap;
    import java.util.Map;
    
    public class Demo06 {
            
        public static void main(String[] args) {
            //1.默认使用TLAB区
            //参数:-Xmx30M -Xms30M -XX:+UseSerialGC -XX:+PrintGCDetails -XX:PretenureSizeThreshold=1000
            //这种现象原因为:虚拟机对于体积不大的对象 会优先把数据分配到TLAB区域中,因此就失去了在老年代分配的机会
            //2.禁用TLAB区
            //参数:-Xmx30M -Xms30M -XX:+UseSerialGC -XX:+PrintGCDetails -XX:PretenureSizeThreshold=1000 -XX:-UseTLAB
            Map<Integer, byte[]> m = new HashMap<Integer, byte[]>();
            for(int i=0; i< 5*1024; i++){   //5*1024次
                byte[] b = new byte[1024];   //每次1024B
                m.put(i, b);
            }
        }
        
    }

    【使用TLAB区域运行结果】

    【禁用TLAB区域运行结果 -XX:-UseTLAB】

    [ 总结 ] 

    使用PretenureSizeTheshold可以直接指定进入老年代的对象大小,但是要注意TLAB区域的优先分配空间。

    【TLAB区域】

    TLAB区域全称是Thread Local Allocation Buffer ,即线程本地分配缓存,从名字上看是一个线程专用的内存专用分配区域,是为了加速对象的分配而诞生的。每一个线程都会产生一个TLAB,该线程独享的工作区域,Java虚拟机使用这种TLAB区域来避免多线程冲突问题,提高了对象分配的效率。TLAB空间一般不会太大,当大对象无法在TLAB分配时,则会直接分配到堆上。

    [ 参数 ]

    -XX:+UseTLAB  设置TLAB

    -XX:+TLABSize  设置TLAB大小

    -XX:TLABRefillWasteFraction  设置维护进入TLAB空间的单个对象的大小,它是一个比例值,默认为64,即如果对象大于整个空间的1/64,则在堆创建对象。

    -XX:+PrintTLAB  查看TLAB信息

    -XX:ResizeTLAB  自调整TLABRefillWasteFraction阈值

     [ 一个对象的创建流程  ]

    一个对象创建在什么位置,我们的Jvm会有一个比较细节的流程,根据数据的大小,参数的设置,决定如果创建分配,以及其位置。

  • 相关阅读:
    toj 2819 Travel
    toj 2807 Number Sort
    zoj 2818 Prairie dogs IV
    zoj 1276 Optimal Array Multiplication Sequence
    toj 2802 Tom's Game
    toj 2798 Farey Sequence
    toj 2815 Searching Problem
    toj 2806 Replace Words
    toj 2794 Bus
    css截取字符
  • 原文地址:https://www.cnblogs.com/HigginCui/p/8445717.html
Copyright © 2011-2022 走看看