转载

任何一个JVM问题都可以深究-垃圾回收

   类似 Object obj = new Object() 这类的强引用,只要强引用还在,垃圾收集器永远不会回收掉被引用的对象。

弱引用

   用来描述非必需对象,但是它的强度比软引用关联的对象只能生存到下一次收集发生之前。软引用用 SoftRefrence 来表示。

虚引用

   一个对象是否有虚引用的存在,完全不会对其生存空间造成影响,也无法通过虚引用获取一个对象实例。 JDK 1.2 后用 PhantomRefrence 来表示。

弱引用

  弱引用来描述非必需对象的,但是它的强度比软引用更弱一些,被弱引用关联的对象只能生存到下一次垃圾回收器之前, JDK 1.2weakRefrence 来表示。

垃圾回收的目的是?

  清除不再使用的引用,自动释放内存。

GC是如何判断对象是否被回收的?

   GCRoot ,如果一个对象与 GC Roots 之间没有直接或间接的引用关系,如:

  • 某个失去任何引用的对象
  • 两个互相环岛循环引用对象

判决这些对象"死缓",是可以被回收的

什么对象可以作为 GC Roots 呢?

  • 类静态属性中引用的对象
  • 常量引用的对象
  • 虚拟机栈中引用的对象
  • 本地方法栈的引用对象

说说垃圾回收的相关算法?

  • 标记-清除算法

  该算法从每个 GC Roots 出发,依次标记有引用关系的对象,最后将没有被标记的对象清除。但是这种算法会带来大量的空间碎片,导致需要分配一块较大的连续空间的时候容易触发 FGC

  • 标记-整理算法

   类似计算机的磁盘整理,首先会从 GC Roots 出发标记为存活对象,然后将存活对象,然后将存活对象整理到内存空间的一端,形成连续的已使用空间,最后把已使用的空间之外的一部分全部清理掉,这样就不会产生空间碎片问题。

  • Mark-Copy 算法

  为了能够将并行地标记和整理将空间分为两块,每次只激活其中一块,垃圾回收时只需把存活的的对象复制到另一块未激活空间上,将未激活的空间标记为已激活,将已激活空间标记为未激活,然后清除空间中的原对象。

  堆内存空间分为 Eden 和两块较小记为未激活,然后清除原空间的元对象。堆内存空间分为较大的 Eden 和两块较小的 Survivor ,每次只使用 EdenSurvivor 区的一块。这种情形下的 Mark-Copy 减少内存空间的浪费。

Mark-Copy 现作为主流的 YGC 算法进行新生代的垃圾回收。

说说你知道的垃圾回收器?

  • Seral

   Seral 回收器是一个主要用于 YGC 的垃圾回收器,采用串行单线程的方式完成 GC 任务,其中 Stop The World 简称 STW ,即垃圾回收的某个阶段会暂停整个应用程序执行。 FGC 的时间相对较长,频繁 FGC 会严重影响应用程序的性能

  • CMS

算法:

  标记-清除算法,会产生大量的空间碎片

解决方案:

   -XX: + UseCMSCommpactAtFullCollection = n 参数,在执行 n 次 FGC 后, JVM 再在老生代执行空间碎片整理,执行一次空间碎片整理,但是空间碎片整理阶段也会引发 STW

步骤:

  1. 初始标记(Initital Mark)
  2. 并发标记(Concurrent Mark)
  3. 重新标记(ReMark)
  4. 并发清除(Concurrent Mark)

问题:

a. 1,3 : 引发STW

解决方案:

   为了减少 STW 次数, GMS 还可以通过配置 -XX: +CMSFullGCsBeforeCompaction = n 参数,在执行 n 次FGC之后, JVM 再在老年代执行空间碎片整理。

b. 2,4 : 可以和应用程序并发执行,也是比较耗时的操作,但并不影响应用程序政策执行。

  • G1

HotSpot 在 JDK7 中推出了 新一代 G1 垃圾回收,通过 -XX: + UseG1GC 参数启动。和 CMS 相比, G1 具备压缩功能,能避免碎片问题, G1 的暂停时间更加可控,性能总体还是不错的

new一个对象干了哪些事情?

   Java 是面向对象的静态强类型语言,声明并创建对象很常见,根据某个类声明一个引用变量指向被创建的对象,并使用此引用变量来操作对象,那么在new一个对象干了哪些事情呢?

字节码角度:

1. NEW

  • 如果找不到 Class 对象,则进行类加载
  • 加载成功后,则在堆中分配内存,从 Object 开始到本地路径上所有的属性都要分配内存
  • 内存分配完毕,进行0值初始化
    • 引用是占据存储空间的,它是一个变量值,占用4个字节
      • 将执向实例对象的引用变量压入虚拟机栈的栈顶

2. DUP

  • 在栈顶复制该引用变量,这时的栈顶有两个指向堆内实例对象引用变量。
    • 如果方法有参数,还需要把参数压入操作栈中。两个引用变量的目的不同,其中压入底下的引用用于赋值,
    • 或者保存到局部变量表,另一个栈顶的引用变量作为句柄调用相关的方法。

3. INVOKESPECIAL :

  • 调用对象实例方法,通过栈顶的引用变量调用方法。是类初始化时执行的方法,而是对象初始化时执行的方法。

执行步骤角度:

1.确认类元信息是否存在:

  • JVM 接收到 new 指令时,首先在 metaspace 内检查需要创建的类元信息是否存在

    • 如果不存在,那么在双亲委托派模式下,使用当前类加载器以 ClassLoader + 包名 + 类名 为 Key 进行查找对应的 .class 文件
      ClassNotFoundException
      Class
      

2.分配对象内存:

  • 计算对象的内存占用空间
    CAS
    

3.设定默认值:

  成员变量值都需要设定位默认值,即各种不同形式的0值

4.设置对象头:

  设置新对象的哈希码,GC信息,锁信息,对象所属的类元信息等。这个过程的具体设置取决于 JVM 实现。

5.执行init方法:

  初始化成员变量,执行实例化代码块,调用类的构造方法,并把堆内对象的首地址赋值给引用变量。

原文  https://juejin.im/post/5dfccc5a6fb9a01616436517
正文到此结束
Loading...