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会有一个比较细节的流程,根据数据的大小,参数的设置,决定如果创建分配,以及其位置。

  • 相关阅读:
    【转】UTF8文件的Unicode签名BOM(Byte Order Mark)问题
    【转】java使用正则表达式去除字符串的html标签
    用UltraEdit转换大小写
    【转】浅谈字节序(Endianness)
    【转】用UltraEdit的正则表达式替换功能来格式化网页源代码
    【转】关于正则表达式匹配任意字符(包括换行符)的写法
    【转】C# 中的 #region 和 #endregion
    网易游戏开发工程师笔试题
    c++笔试题汇总
    恒生电子面试过程纪录
  • 原文地址:https://www.cnblogs.com/HigginCui/p/8445717.html
Copyright © 2011-2022 走看看