标签:自动化 条件 垃圾回收 这一 实例 大于 ble str 大小
Java语言是一门自动内存管理的语言,不再需要的对象可以通过垃圾回收自动进行内存释放。
JVM将Java程序运行时内存区域划分成以下几个部分:
程序计数器可以看做是当前线程所执行字节码的行号指示器。JVM依靠程序计数器来选取需要执行的下一条字节码指令,分支、循环、跳转、异常处理、线程恢复等基础功能都需要依赖这个计数器来实现。
虚拟机栈描述了Java方法执行的内存模型:每个方法在执行的同时都会创建一个栈帧用于存储局部变量表、操作数栈、动态链接、方法出口等信息。每个方法从调用开始至结束的过程,就对应着一个栈帧在虚拟机栈中入栈到出栈的过程。
本地方法栈与java虚拟机栈类似,只不过它用于为虚拟机使用的Native方法服务。
Java堆用于存放对象实例数据,几乎所有的对象实例都在这里分配内存。Java堆是垃圾收集器管理的主要区域。
方法区用于存储已经被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。
其中程序计数器、Java虚拟机栈和本地方法栈都是线程私有的,而java堆和方法区是线程所共享的。
Java垃圾回收主要作用于Java堆。而Java堆又划分为年轻代,老年代。年轻代又分为Eden,Survivor(分为From Survivor和To Survivor)。Java中垃圾回收是分带收集的,不同区域的回收算法是不同的。
判定一个对象是否已死的方法主要有两种:引用计数法和可达性分析算法(也叫根结点枚举法)。
引用计数法思想很简单:为每个对象维持一个引用计数器,当有一个地方引用它时,计数器加1;当引用失效时,计数器减1;任何时刻计数器为0的对象就是可以被回收的对象。
引用计数法的优点非常明显:实现简单,效率高。但是它无法解决循环引用问题:例如对象A和B互相引用,但是其他任何对象都没有引用A和B或者被A和B引用,此时引用计数法判定A和B不可回收,但是事实上A和B都可以被回收。为了解决循环引用问题,可达性分析算法应运而生。
可达性分析算法思想是通过一个"GC Roots"集来判定对象是否已死,以"GC Roots"为起始点,从这些节点往下搜索,搜索所走过的路径称为引用链,当一个对象到GC Roots没有任何引用链相连时,这个对象就是不可用的。示意图如下:
在Java中,可作为 GC Root的对象包括以下几种:
算法的执行过程与名字一样,先标记所有需要回收的对象,在标记完成后统一回收所有被标记的对象。该算法有两个问题:
复制算法是为了解决标记-清除算法的效率问题的,其思想如下:将可用内存的容量分为大小相等的两块,每次只使用其中的一块,当这一块内存使用完了,就把存活着的对象复制到另外一块上面,然后再把已使用过的内存空间清理掉。
现在的商业虚拟机都采用这种收集算法来回收新生代,新生代中的对象98%是“朝生夕死”的,所以并不需要按照1∶1的比例来划分内存空间,而是将内存分为一块较大的Eden空间和两块较小的Survivor空间,每次使用Eden和其中一块Survivor。
当回收时,将Eden和Survivor中还存活着的对象一次性地复制到另外一块Survivor空间上,最后清理掉Eden和刚才用过的Survivor空间。HotSpot虚拟机默认Eden和Survivor的大小比例是8∶1,也就是每次新生代中可用内存空间为整个新生代容量的90%,只有10%的内存会被“浪费”。
当然,90%的对象可回收只是一般场景下的数据,我们没有办法保证每次回收都只有不多于10%的对象存活,当Survivor空间不够用时(例如,存活的对象需要的空间大于剩余一块Survivor的空间),需要依赖其他内存(这里指老年代)进行分配担保(Handle Promotion)。
复制收集算法在对象存活率较高时就要进行较多的复制操作,效率将会变低。更关键的是,如果不想浪费50%的空间,就需要有额外的空间进行分配担保,以应对被使用的内存中所有对象都100%存活的极端情况,所以在老年代一般不能直接选用这种算法。
与标记-清除算法过程一样,只不过在标记后不是对未标记的内存区域进行清理,二是让所有的存活对象都向一端移动,然后清理掉边界外的内存。该方法主要用于老年代。
目前商用虚拟机都使用“分代收集算法”,所谓分代就是根据对象的生命周期把内存分为几块,一般把Java堆中分为新生代和老年代,这样就可以根据对象的“年龄”选择合适的垃圾回收算法。
JVM有7种不同的垃圾收集器,它们是垃圾收集算法的具体实现。下图展示了这7种作用于不同分代的收集器,其中用于回收新生代的收集器包括Serial、PraNew、Parallel Scavenge,回收老年代的收集器包括Serial Old、Parallel Old、CMS,还有用于回收整个Java堆的G1收集器。不同收集器之间的连线表示它们可以搭配使用。
各个收集器的特点如下:
收集器 | 串行、并行or并发 | 新生代or老年代 | 算法 | 目标 | 适用场景 |
---|---|---|---|---|---|
Serial | 串行 | 新生代 | 复制算法 | 响应速度优先 | 单CPU环境下的Client模式 |
Serial Old | 串行 | 老年代 | 标记-整理 | 响应速度优先 | 单CPU环境下的Client模式、CMS的后备预案 |
ParNew | 并行 | 新生代 | 复制算法 | 响应速度优先 | 多CPU环境时在Server模式下与CMS配合 |
Parallel Scavenge | 并行 | 新生代 | 复制算法 | 吞吐量优先 | 在后台运算而不需要太多交互的任务 |
Parallel Old | 并行 | 老年代 | 标记-整理 | 吞吐量优先 | 在后台运算而不需要太多交互的任务 |
CMS | 并发 | 老年代 | 标记-清除 | 响应速度优先 | 集中在互联网站或B/S系统服务端上的Java应用 |
G1 | 并发 | 年轻代和老年代 | 标记-整理+复制算法 | 响应速度优先 | 面向服务端应用,将来替换CMS |
Java技术体系中所提倡的自动内存管理最终可以归结为自动化地解决了两个问题:给对象分配内存 以及 回收分配给对象的内存。一般而言,对象主要分配在新生代的Eden区上,如果启动了本地线程分配缓存(TLAB),将按线程优先在TLAB上分配。少数情况下也可能直接分配在老年代中。总的来说,内存分配规则并不是一层不变的,其细节取决于当前使用的是哪一种垃圾收集器组合,还有虚拟机中与内存相关的参数的设置。
对象优先在Eden分配,当Eden区没有足够空间进行分配时,虚拟机将发起一次MinorGC。现在的商业虚拟机一般都采用复制算法来回收新生代,将内存分为一块较大的Eden空间和两块较小的Survivor空间,每次使用Eden和其中一块Survivor。 当进行垃圾回收时,将Eden和Survivor中还存活的对象一次性地复制到另外一块Survivor空间上,最后处理掉Eden和刚才的Survivor空间。(HotSpot虚拟机默认Eden和Survivor的大小比例是8:1)当Survivor空间不够用时,需要依赖老年代进行分配担保。
大对象直接进入老年代。所谓的大对象是指,需要大量连续内存空间的Java对象,最典型的大对象就是那种很长的字符串以及数组。
长期存活的对象将进入老年代。当对象在新生代中经历过一定次数(默认为15)的Minor GC后,就会被晋升到老年代中。
动态对象年龄判定。为了更好地适应不同程序的内存状况,虚拟机并不是永远地要求对象年龄必须达到了MaxTenuringThreshold才能晋升老年代,如果在Survivor空间中相同年龄所有对象大小的总和大于Survivor空间的一半,年龄大于或等于该年龄的对象就可以直接进入老年代,无须等到MaxTenuringThreshold中要求的年龄。
需要注意的是,Java的垃圾回收机制是Java虚拟机提供的能力,用于在空闲时间以不定时的方式动态回收无任何引用的对象占据的内存空间。也就是说,垃圾收集器回收的是无任何引用的对象占据的内存空间而不是对象本身。
Minor GC触发条件:当Eden区满时,触发Minor GC。
Full GC触发条件:
标签:自动化 条件 垃圾回收 这一 实例 大于 ble str 大小
原文地址:https://www.cnblogs.com/zdcsmart/p/12576471.html