1,常用的垃圾回收算法

常用的垃圾回收算有:标记清除算法,赋值算法,标记-压缩算法,分代收集算法。
(1)标记-清除算法:原理就是将要所有回收的对象做标记,然后统一回收要被回收的对象。
(2)复制算法:原理就是将内存划分为两个大小相同的两块,当其中一块被使用完毕后,会把还存活
的对象复制到另一个内存中,然后把存满的内存块对象全部回收。
(3)标记压缩算法:原理就是给将要回收的对象做标记,不直接回收,而是把需要回收的放置到一 端,然后剩下的放在另一端,把需要回收的那端回收。
(4)分代收集算法:原理就是将对象区分为新生代和老年代两类对象,然后根据不同代的回收特点去回收。

2, JVM中的GC怎么判断对象是否可回收

(1) 引用计数器:给对象添加一个引用计数器,当有一个地方引用它时,计数器的值就加1,;当引用失效时,计数器的值就减1,如果一个对象的引用计数器为0则表示这个对象已不可能再次被引用,GC就可进行回收,这种算法在实际中是存在缺陷的并且JVM本身采用的也不是此种算法。实际上,Java语言为了避免上述算法存在的缺陷,其通过一系列名为“GC Roots”的对象作为起始点,从这个节点开始向下搜索,从而形成许多引用路径链,当一个对象没有任何一条引用链可以抵达起始点时,则说明此对象已经不会再被引用,则可进行回收,在Java语言中,可作为GC Roots对象主要包括以下几种:
[

](https://blog.csdn.net/Peppa_Pig_0325/article/details/86433592)

  • 虚拟机栈(栈帧中的本地变量表)中的引用对象
  • 方法区中的类静态属性引用的对象
  • 方法区中的常量引用的对象
  • 本地方法栈中JNI(即一般说的Native方法)引用的对象。

(2) GC怎样进行回收:

image.png

在GC正式执行回收前,首先要提到实例对象的finalize()方法,实例对象会有一个计数器记录这个方法的调用次数,即未调用过计数器属性值为0,调用过计数器属性值为1。GC在判断过哪些对象没有被引用后,会进一步判断finalize()方法是否被调用过,如果已经被调用过直接进行回收,如果没有被调用过则会将这些对象放入一个名为F-Queue的队列之中,然后再对队列中的实例执行finalize()方法,在finalize()方法内部可完成实例的重新被引用(如下代码),然后GC会再次对F-Queue队列中实例对象进行整理,将没有被引用的对象进行回收,将重新被引用的对象从队列F-Queue删除,至此一次GC垃圾回收完成,下次GC的垃圾回收仍会按照这种执行流程执行。

2 GC常用的算法

什么是垃圾(garbage)

没有引用指向的一个或者多个对象叫垃圾

如何找到这些垃圾:

一般有引用计数法(reference count)和根可达算法,
1.第一种叫做引用计数法,有一个引用指向一个对象,计数就加1,当引用失效时就减去1,直到这个数为0,就会被当作垃圾。
2.引用计数不能解决一个问题(循环引用),如果根据引用计数法,这些都不是垃圾,可是没有其他引用指向这一团,那他们就是一团垃圾,
根可达算法解决这一问题
根可达的意思就是从跟上开始搜索,当程序启动后马上需要的对象就叫做根对象,Root Seaching是首先找到跟对象,然后从根对象往下一直找,找到那些呗引用的对象。
image.png

名词解释:

  • 根可达算法:从根上对象开始搜索,
  • 线程栈变量:一个main方法开始运行,main线程栈中的变量调用了其他方法,main栈中的方法访问到的对象叫跟对象
  • 静态变量:T.class对静态变量初始化能够访问到的对象叫做根对象,
  • 常量池:如果一个class能够用到其他class对象叫做跟对象,
  • JNI指针:如果调用了本地方法,本地的对象就叫做跟对象;
  • 根对象:如果一个程序启动马上就能用到的对象叫做跟对象

    GC Aldorithms(常见的垃圾回收算法)

    GC常见的算法有三个:

    1. - Mark-Sweep(标记清除算法)
    2. - Copying(复制/拷贝算法)
    3. - Mark-Compact(标记整理/标记压缩算法)
  • 第一个是标记清除算法:把垃圾标记出来,然后把对像清理掉,首先找到那些有用的,没有用的标记出来然后清理掉
  • image.png

image.png
标记清除算法相对简单,在存活对象比较多的时候效率比较高,这种算法需要扫描两遍,第一遍是找到有用的,第二遍是把那些没有用的找出来然后清理掉,执行效率偏低,容易产生碎片。
第二个是复制算法:就是把内容一分为二,把有用的拷贝到一边,然后把剩下的全部清除,适用于存活对象较少的情况,只扫描一次,效率提高了很多,而且没有碎片产生,但是浪费空间,移动复制对象要调整对象的引用

image.png
image.png
第三个是标记整理算法:就是把所有的对象整理的过程,清理的同时压缩,有用的全部往前走,剩下的大片空间就清理出来了,空间连续而且没有碎片
标记清理算法也有他自己的问题,在这个过程中需要扫描两次,还要移动对象,第一次先找出来有用的,第二遍才开始移动,移动过程如果是多线程的还需要进行同步,效率降低很多,但是不会产生碎片,对象分配比较方便,不会产生内存减半
image.png

image.png

3 GC在堆内存的工作过程

堆是java虚拟机进行垃圾回收的主要场所,其次要场所是方法区,
在JDK1.8以后,堆永远的取消了,由元空间取代,
Java将堆内存分为3部分:新生代,老年代,永久代。其中新生代又分为Eden,SO,S1(Survivor=s0,s1)
如图:
image.png
堆内存上对象的分配与回收:
我们创建的对象会优先在Eden分配,如果是大对象(很长的字符串数组),则可以直接进入老年代。虚拟机提供一个-XX:PretenureSizeThreadhold参数(老年代分配(仅对SSerial和ParNew有效),令大于这个参数值的对象直接在老年代中分配,避免在Eden区和两个Survivor区发生大量的内存拷贝。
另外,长期存活的对象将进入老年代,每一次MinorGC(年轻代GC),对象年龄就大一岁,默认15岁晋升到老年代,通过
-XX:MaxTenuringThreshold(年龄阈值)设置晋升年龄。
堆内存上的对象回收也叫做垃圾回收,那么垃圾回收什么时候开始呢?
垃圾回收主要是完成清理对象,整理内存的工作。垃圾回收分为年轻代区域发生的Minor GC和老年代区域发生的Full GC
Minor GC(年轻代垃圾回收):对象优先在Eden中分配,当Eden中没有足够空间的时候虚拟机将发生一次Minor GC ,因为Java大多数对象都是朝生夕灭的,所以Minor GC 非常频繁,速度也很快。
Full GC(老年代垃圾回收):Full GC是指发生在老年代的GC,当老年代没有足够的空间时即发生Full GC,发生Full GC一般都会有一次Minor GC

Minor GC 和Full GC触发的条件:

动态对象年龄判定:
如果Survivor空间中相同年龄所有对象的大小总和大于Survivor空间的一半,那么年龄大于等于该对象年龄的对象即可晋升到老年代,那么年龄大于等于该对象年龄的对象即可晋升到老年代,不必等达到年龄阈值
空间分配担保:
发生Minor GC 时,虚拟机会检测之前每次晋升到老年代的平均年龄是否大于老年代的剩余空间大小。如果大于,则进行Full GC ,如果小于则查看,
HandlePromotionFailure设置是否允许担保失败,如果允许,则会进行一次Minor GC ,如果不允许,则改为进行一次Full GC

4 GC的回收器(CMS和G1)?

JVM中的垃圾收集器主要包括7种,即Serial,Serial Old,ParNew,Parallel Scavenge,Parallel Old以及CMS,G1收集器。如下图所示:

image.png

1、Serial收集器:
Serial收集器是一个单线程的垃圾收集器,并且在执行垃圾回收的时候需要~~ ~~Stop The World。虚拟机运行在Client模式下的默认新生代收集器。Serial收集器的优点是简单高效,对于限定在单个CPU环境来说,Serial收集器没有多线程交互的开销。
2、Serial Old收集器:
Serial Old是Serial收集器的老年代版本,也是一个单线程收集器。主要也是给在Client模式下的虚拟机使用。在Server模式下存在主要是做为CMS垃圾收集器的后备预案,当CMS并发收集发生Concurrent Mode Failure时使用。
3、ParNew收集器:
ParNew是Serial收集器的多线程版本,新生代是并行的(多线程的),老年代是串行的(单线程的),新生代采用复制算法,老年代采用标记整理算法。可以使用参数:-XX:UseParNewGC使用该收集器,使用 -XX:ParallelGCThreads可以限制线程数量。
4、Parallel Scavenge垃圾收集器:
Parallel Scavenge是一种新生代收集器,使用复制算法的收集器,而且是并行的多线程收集器。Paralle收集器特点是更加关注吞吐量(吞吐量就是cpu用于运行用户代码的时间与cpu总消耗时间的比值)。可以通过-XX:MaxGCPauseMillis参数控制最大垃圾收集停顿时间;通过-XX:GCTimeRatio参数直接设置吞吐量大小;通过-XX:+UseAdaptiveSizePolicy参数可以打开GC自适应调节策略,该参数打开之后虚拟机会根据系统的运行情况收集性能监控信息,动态调整虚拟机参数以提供最合适的停顿时间或者最大的吞吐量。自适应调节策略是Parallel Scavenge收集器和ParNew的主要区别之一。
5、Parallel Old收集器:
Parallel Old是Parallel Scavenge收集器的老年代版本,使用多线程和标记-整理算法。
6、CMS(Concurrent Mark Sweep)收集器(并发标记清除)
CMS收集器是一种以获取最短回收停顿时间为目标的收集器。CMS收集器是基于标记-清除算法实现的,是一种老年代收集器,通常与ParNew一起使用。
CMS的垃圾收集过程分为4步:

  1. - **初始标记**:需要“Stop the World”,初始标记仅仅只是标记一下GC Root能直接关联到的对象,速度很快。
  2. - **并发标记**:是主要标记过程,这个标记过程是和用户线程并发执行的。
  3. - **重新标记**:需要“Stop the World”,为了修正并发标记期间因用户程序继续运作而导致标记产生变动的那一部分对象的标记记录(停顿时间比初始标记长,但比并发标记短得多)。
  4. - **并发清除**:和用户线程并发执行的,基于标记结果来清理对象。

image.png

那么问题来了,如果在重新标记之前刚好发生了一次MinorGC,会不会导致重新标记阶段Stop the World时间太长?

答:不会的,在并发标记阶段其实还包括了一次并发的预清理阶段,虚拟机会主动等待年轻代发生垃圾回收,这样可以将重新标记对象引用关系的步骤放在并发标记阶段,有效降低重新标记阶段Stop The World的时间。

CMS垃圾回收器的优缺点分析:

CMS以降低垃圾回收的停顿时间为目的,很显然其具有并发收集,停顿时间低的优点。

缺点主要包括如下:

  • 对CPU资源非常敏感,因为并发标记和并发清理阶段和用户线程一起运行,当CPU数变小时,性能容易出现问题。
  • 收集过程中会产生浮动垃圾,所以不可以在老年代内存不够用了才进行垃圾回收,必须提前进行垃圾收集。通过参数-XX:CMSInitiatingOccupancyFraction的值来控制内存使用百分比。如果该值设置的太高,那么在CMS运行期间预留的内存可能无法满足程序所需,会出现Concurrent Mode Failure失败,之后会临时使用Serial Old收集器做为老年代收集器,会产生更长时间的停顿。
  • 标记-清除方式会产生内存碎片,可以使用参数-XX:UseCMSCompactAtFullCollection来控制是否开启内存整理(无法并发,默认是开启的)。参数-XX:CMSFullGCsBeforeCompaction用于设置执行多少次不压缩的Full GC后进行一次带压缩的内存碎片整理(默认值是0)。

    浮动垃圾:

    由于在应用运行的同时进行垃圾回收,所以有些垃圾可能在垃圾回收进行完成时产生,这样就造成了“Floating Garbage”,这些垃圾需要在下次垃圾回收周期时才能回收掉。所以,并发收集器一般需要20%的预留空间用于这些浮动垃圾。

    7、G1(Garbage-First)收集器:

    G1收集器将新生代和老年代取消了,取而代之的是将堆划分为若干的区域,每个区域都可以根据需要扮演新生代的Eden和Survivor区或者老年代空间,仍然属于分代收集器,区域的一部分包含新生代,新生代采用复制算法,老年代采用标记-整理算法。
    通过将JVM堆分为一个个的区域(region),G1收集器可以避免在Java堆中进行全区域的垃圾收集。G1跟踪各个region里面的垃圾堆积的价值大小(回收所获得的空间大小以及回收所需时间的经验值),在后台维护一个优先列表,每次根据回收时间来优先回收价值最大的region。
    G1收集器的特点:

    1. - **并行与并发**:G1能充分利用多CPU,多核环境下的硬件优势,来缩短Stop the World,是并发的收集器。
    2. - **分代收集**:G1不需要其他收集器就能独立管理整个GC堆,能够采用不同的方式去处理新建对象、存活一段时间的对象和熬过多次GC的对象。
    3. - **空间整合**:G1从整体来看是基于标记-整理算法,从局部(两个Region)上看基于复制算法实现,G1运作期间不会产生内存空间碎片。
    4. - **可预测的停顿**:能够建立可以预测的停顿时间模型,预测停顿时间。

和CMS收集器类似,G1收集器的垃圾回收工作也分为了四个阶段:

  1. - 初始标记
  2. - 并发标记
  3. - 最终标记
  4. - 筛选回收

其中,筛选回收阶段首先对各个Region的回收价值和成本进行计算,根据用户期望的GC停顿时间来制定回收计划。


5 Java中4种引用及区别?

在Java语言中,除了基本类型以外,其余对象均以“引用”的形式传递。在调用函数传参时,实际上传递的是指向对象的“引用”,并非对象的实际值。

什么是引用的传递?

举例来说,也就是:函数A,创建了对象objectA,作为参数传递给函数B,函数B对传进来的参数进行了修改,回到函数A时,对象ojbectA依然被修改。

  • image.png

    四种引用方式

    1 强引用:是Java最常用的引用,当给对象用‘=’赋值一个新的对象,此时对象的引用就是强引用,
    Object o1 = new ClassMemory();
    适用于所有的场景;
    强引用的 生命周期 是最长的引用,
    特点是直到被显示的清除引用(赋值为null)前,该引用会一直存在,宁可抛出OOM(OutOfMemoryError内存溢出),也不会清理其内存的一种引用类型。
    2 软引用:,软引用是指用来描述一些有用但是不是必需的对象,只有在内存不足时,才会将其清理。
    SoftReference<ClassMemory> o1 = new SoftReference<>(new ClassMemory());
    适用于缓存
    生命周期: 次于强引用
    软引用的特点是,gc并不会将其清理,只有在jvm认为内存不足时,在出发OOM之前,清理软引用;这一点很好的解决了OOM的问题,并且这个特性很适合用来实现缓存;软引用可以和一个引用队列联合使用,如果软引用所引用的对象被JVM回收,这个软引用就会被加入到与之关联的引用队列中,
    3 弱引用与软引用类似,弱引用是指用来描述非必需对象,当JVM进行垃圾回收的时候,无论内存是否充足,都会回收被弱引用关联的对象
    适用于缓存
    弱引用的特点是,在jvm认为内存不足时,在出发OOM之前,清理软引用;除此之外,在触发gc时,弱引用同样会被清理。
    生命周期: 次于弱引用
    4 虚引用:跟其他三种引用有很大的不同,它的get()方法永远返回null,这正是它名字的由来,如果一个对象虚引用关联,则根没有引用与之关联一样,在任何时候都可能被垃圾回收
    作用:用来跟踪对象被垃圾回收的活动。当垃圾回收器准备一个对象时,如果发现他还有虚引用,就会把这个虚引用加入到与之关联的引用队列中,成相互可以通过判断引用队列中是否已经加入到引用队列中,那么就可以在所引用的对象的内存被回收之前采取必要的行动。
    特点:被软引用关联的对象只有在内存不足时才会被回收,而被弱引用关联的对象在JVM进行垃圾回收时总会被回收。针对上面的特性,软引用适合用来进行缓存,当内存不够时能让JVM回收内存,弱引用能用来在回调函数中防止内存泄露。虚引用必须和引用队列(ReferenceQueue)联合使用。

    6 final、finally与finalize的区别?

    final 用于声明属性,方法和类,分别表示属性不可变,方法不可覆盖,类不可以被继承;
    finally是异常处理 语句结构的一部分,表示总是执行
    finalize是Object类的一个方法,所有对象都有此方法,在垃圾收集器执行的时候会调用被回收对象的此方法,可以覆盖此方法提供垃圾收集时的其他资源回收,例如关闭文件等

    7 哪些可以作为GC ROOTS?

  1. 虚拟栈引用的对象
  2. 本地栈中调用Native方法的对象
  3. 方法区类静态变量引用的对象
  4. 方法区常量引用的对象

    8 GC 相关参数 - 调优

参考网址:http://t.zoukankan.com/dmzna-p-12913458.html