资讯专栏INFORMATION COLUMN

java垃圾收集算法

venmos / 1098人阅读

摘要:新生代收集器,复制算法,并行收集,面向吞吐量要求吞吐量优先收集器。吞吐量用户代码运行时间用户代码运行时间垃圾回收时间控制最大垃圾收集停顿时间,大于零的毫秒数。吞吐量大小,到的整数,垃圾收集时间占总时间的比例,计算时间占用比例。

基础背景 运行时数据区域
虚拟机结构图

程序计数器:

因为线程会切换,因此每个线程独有一份,用作在执行过程中记录编译后的class文件行号.

虚拟机栈:以栈帧为单位存放局部变量.

Native方法栈:和虚拟机栈类似,不过,一个本地方法是这样一个方法:该方法的实现由非java语言实现,比如C语言实现。很多其它的编程语言都有这一机制,比如在C++中,你可以告知C++编译器去调用一个C语言编写的方法.我们知道,当一个类第一次被使用到时,这个类的字节码会被加载到内存,并且只会回载一次。在这个被加载的字节码的入口维持着一个该类所有方法描述符的list,这些方法描述符包含这样一些信息:方法代码存于何处,它有哪些参数,方法的描述符(public之类)等等。如果一个方法描述符内有native,这个描述符块将有一个指向该方法的实现的指针。这些实现在一些DLL文件内,但是它们会被操作系统加载到java程序的地址空间。当一个带有本地方法的类被加载时,其相关的DLL并未被加载,因此指向方法实现的指针并不会被设置。当本地方法被调用之前,这些DLL才会被加载,这是通过调用java.system.loadLibrary()实现的。

方法区:运行时常量池,存放编译器的字面量和符号引用,也可以在运行时动态加入.

java堆:存放对象的实例,是垃圾回收的主战场,

创建一个对象

比如执行 new MyClass();

去常量池中寻找,查看类是否被加载.如果没加载,则加载class.

在java堆中分配内存空间,方式有以下两种:

指针碰撞:把指针向空闲对象移动与对象占用内存大小相等的距离,使用的收集器有Serial、ParNes等

空闲列表:虚拟机维护一个列表,记录可用的内存块,分配给对象列表中一块足够大的内存空间,使用的收集器有CMS等.

如何分配内存,由垃圾回收器决定.

内存的具体分配过程中有同步和预留空白区的方式

内存分配好后,再执行init()方法,初始化实例.

对象头

对象头主要记录对象的hashcode,GC标记,元数据地址,以及关于对象锁的使用,年龄代,偏向线程等。

hash用于快速寻找对象

对象头大小32bit/64bit,由虚拟机决定

实例数据区的数据类型,按照相似放在一起.

对象中的访问定位
方式

句柄池

句柄池从堆中划分

由实例地址和类型数据地址构成

指针

可直接通过指针访问到实例对象

优劣

句柄的使用,方便了实例位置的改变,可以不改变引用,但是访问速度相对于指针低一些.

JVM垃圾回收 判断可否回收的算法

1.引用计数算法:
给对象中添加一个引用计数器,每当有一个地方引用它时,计数器值就加1;当引用失效时,计数器值就减1;任何时刻计数器都为0的对象就是不再被使用的,垃圾收集器将回收该对象使用的内存。

可达性分析算法:

通过一系列的名为GC Root的对象作为起点,从这些节点向下搜索,搜索所走过的路径称为引用链,当一个对象到GC Root没有任何引用链相连时,则该对象不可达,该对象是不可使用的,垃圾收集器将回收其所占的内存。
实例的位置:

java虚拟机栈(栈帧中的本地变量表)中的引用的对象。

方法区中的类静态属性引用的对象。

方法区中的常量引用的对象。

本地方法栈中JNI本地方法的引用对象。

不可达对象到死亡还需要两次标记,第一次,标记后进入F-Queue队列,第二次标记时只有finalize()中有拯救自己的方法的实例才能自救成功,比如将自己应用给其它变量.

垃圾回收算法
方法区的回收
常量池的回收

没有被引用,即可被回收

class对象回收

所有实例都被回收

所有classLoader都被回收

java.lang.class对象没有被任何地方引用,即无法在任何地方使用反射访问类.

最终是否被回收,还得看JVM参数配置

java堆回收算法

标记清除算法: 先标记判定,再一次性清除.

产生了大量碎片,且效率低下

复制算法: 把可用内存划分为两块,一块用完后,就将活下来的实例放到另一块内存区.

优缺点:没有了碎片化问题,但内存大小减少了一半

标记整理算法: 在标记-清除算法基础上做了改进,标记阶段是相同的标记出所有需要回收的对象,在标记完成之后不是直接对可回收对象进行清理,而是让所有存活的对象都向一端移动,在移动过程中清理掉可回收的对象,这个过程叫做整理。

标记-整理算法相比标记-清除算法的优点是内存被整理以后不会产生大量不连续内存碎片问题。复制算法在对象存活率高的情况下就要执行较多的复制操作,效率将会变低,而在对象存活率高的情况下使用标记整理算法效率会大大提高。

分代收集算法: 根据内存中对象的存活周期不同,将内存划分为几块,java的虚拟机中一般把内存划分为新生代和年老代,当新创建对象时一般在新生代中分配内存空间,当新生代垃圾收集器回收几次之后仍然存活的对象会被移动到年老代内存中,当大对象在新生代中无法找到足够的连续内存时也直接在年老代中创建。现在的Java虚拟机就联合使用了分代复制、标记-清除和标记-整理算法.
java虚拟机垃圾收集器关注的内存结构如下:

新生代

研究表明,新生代中98%的对象是朝生夕死的短生命周期对象,所以不需要将新生代划分为容量大小相等的两部分内存,而是将新生代分为Eden区,Survivor from和Survivor to三部分,其占新生代内存容量默认比例分别为8:1:1,其中Survivor from和Survivor to总有一个区域是空白,只有Eden和其中一个Survivor总共90%的新生代容量用于为新创建的对象分配内存,只有10%的Survivor内存浪费,当新生代内存空间不足需要进行垃圾回收时,仍然存活的对象被复制到空白的Survivor内存区域中,Eden和非空白的Survivor进行标记-清理回收,两个Survivor区域是轮换的。

年老代

年老代中的对象一般都是长生命周期对象,对象的存活率比较高,因此在年老代中使用标记-整理垃圾回收算法。

Java虚拟机对年老代的垃圾回收称为MajorGC/Full GC,次数相对比较少,每次回收的时间也比较长。

堆分配和回收策略
分配

优先在Eden上分配,空间不足,虚拟机发起minor GC.

大对象直接进入老年代,防止折磨新生代空间.[参数设置 -XX:PretrnureSizeThreshold=[字节数]]

长大后的对象进入老年代,在survivor中熬过一次,就长一岁,15岁时就进入老年代[阈值设置 -XX:MaxTenuringThreshold=[岁数]]

相同年龄的对象,若大于或等于空间的一半,也直接进入老年代.

oracle java虚拟机

官方使用的HotSpot虚拟机

默认老年代收集器MarkSweep,新生代收集器Scavenge

MarkSweep收集器:是以牺牲吞吐量为代价来获得最短回收停顿时间的垃圾回收器。对于要求服务器响应速度的应用上,这种垃圾回收器非常适合。在启动JVM参数加上-XX:+UseConcMarkSweepGC ,这个参数表示对于老年代的回收采用CMS。CMS采用的基础算法是:标记—清除。

流程:

初始标记 :在这个阶段,需要虚拟机停顿正在执行的任务,官方的叫法STW(Stop The Word)。这个过程从垃圾回收的"根对象"开始,只扫描到能够和"根对象"直接关联的对象,并作标记。所以这个过程虽然暂停了整个JVM,但是很快就完成了。
并发标记 :这个阶段紧随初始标记阶段,在初始标记的基础上继续向下追溯标记。并发标记阶段,应用程序的线程和并发标记的线程并发执行,所以用户不会感受到停顿。
并发预清理 :并发预清理阶段仍然是并发的。在这个阶段,虚拟机查找在执行并发标记阶段新进入老年代的对象(可能会有一些对象从新生代晋升到老年代, 或者有一些对象被分配到老年代)。通过重新扫描,减少下一个阶段"重新标记"的工作,因为下一个阶段会Stop The World。
重新标记 :这个阶段会暂停虚拟机,收集器线程扫描在CMS堆中剩余的对象。扫描从"跟对象"开始向下追溯,并处理对象关联。
并发清理 :清理垃圾对象,这个阶段收集器线程和应用程序线程并发执行。
并发重置 :这个阶段,重置CMS收集器的数据结构,等待下一次垃圾回收。

新生代收集器,复制算法并行收集,面向吞吐量要求(吞吐量优先收集器)。
吞吐量=用户代码运行时间/(用户代码运行时间+垃圾回收时间)
-XX:MaxGCPauseMillis:控制最大垃圾收集停顿时间,大于零的毫秒数。
-XX:GCTimeRatio:吞吐量大小,0到100的整数,垃圾收集时间占总时间的比例,计算1/(1+n)gc时间占用比例。
-XX:UseAdaptiveSizePolicy:打开之后,就不需要设置新生代大小(-Xmn),Edian,survivor比例及(-XX:SurvivorRatio)晋升老年代年龄(-XX:PretenureSizeThreshold),虚拟机根据系统运行状况,调整停顿时间,吞吐量, GC自适应调节策略,区别parnew。

附:JVM参数整理
参数调优建议

永久代:-XX:PermSize20M -XX:MaxPermSize20M

堆大小: -Xms20M -Xmx20M

新生代: -Xmn10M

Eden与survior比率: -XX:SurvivorRation=8

让大对象直接进入老年代: -XX:PretrnureSizeThreshold=1B

年龄阈值:-XX:MaxTenuringThreshold=15

日志命令:参考连接

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/73706.html

相关文章

  • 搞定JVM垃圾回收就是这么简单

    摘要:之前的堆内存示意图从上图可以看出堆内存的分为新生代老年代和永久代。对象优先在区分配目前主流的垃圾收集器都会采用分代回收算法,因此需要将堆内存分为新生代和老年代,这样我们就可以根据各个年代的特点选择合适的垃圾收集算法。 上文回顾:《可能是把Java内存区域讲的最清楚的一篇文章》 写在前面 本节常见面试题: 问题答案在文中都有提到 如何判断对象是否死亡(两种方法)。 简单的介绍一下强引用...

    taohonghui 评论0 收藏0
  • 深入理解虚拟机之垃圾回收

    摘要:深入理解虚拟机高级特性与最佳实践第二版读书笔记与常见面试题总结上篇文章传送门深入理解虚拟机之内存区域本节常见面试题推荐带着问题阅读,问题答案在文中都有提到如何判断对象是否死亡两种方法。虚引用主要用来跟踪对象被垃圾回收的活动。 《深入理解Java虚拟机:JVM高级特性与最佳实践(第二版》读书笔记与常见面试题总结 上篇文章传送门: 深入理解虚拟机之Java内存区域 本节常见面试题(推荐带着...

    IamDLY 评论0 收藏0
  • JAVA虚拟机

    摘要:它一般运行在模式下的虚拟机。设置最大垃圾收集停顿时间设置吞吐量大小开关参数,打开以后就由虚拟机自动调节策略。 Java内存区域 showImg(https://user-gold-cdn.xitu.io/2019/5/21/16adb14e60767c12); 程序计数器:当前线程所执行字节码的行号指示器,字节码解释器工作时就是通过改变这个计数器的值来选取下一条需要执行的字节码指令 虚拟机...

    edagarli 评论0 收藏0
  • JAVA 垃圾收集器与内存分配策略

    摘要:引言垃圾收集技术并不是语言首创的,年诞生于的是第一门真正使用内存动态分配和垃圾收集技术的语言。垃圾收集器所关注的就是这部分内存。收集器是收集器的多线程版,它是第一款并发收集器。经常出现大对象会导致多次出发垃圾收集。 引言 垃圾收集技术并不是Java语言首创的,1960年诞生于MIT的Lisp是第一门真正使用内存动态分配和垃圾收集技术的语言。垃圾收集技术需要考虑的三个问题是: 哪些内存需...

    AlanKeene 评论0 收藏0

发表评论

0条评论

venmos

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<