java是一个面向对象的静态强类型语言,根据某个类声明一个引用变量指向被创建的对象,并使用此引用对象操作该对象。
那么在实例化对象的过程中,JVM会发生什么呢??

Object object = new Object();字节码为例子来分析,使用命令javap -verbose -p xx.class

stack=2, locals=2, args_size=1
         0: new           #2                  // class java/lang/Object
         3: dup
         4: invokespecial #1                  // Method java/lang/Object."<init>":()V
         7: astore_1
         8: return

前章回顾

上一节讲到了java虚拟机的内存结构:相对于线程而言,分为线程共享线程私有两大类。

线程共享

堆、元空间。

  • 堆空间中存放所有的实例对象,由垃圾收集器自动回收,分新生代和老年代,新生代中有一个Eden区和两个Suvivor区,新生对象在Eden区中被使用,发生GC就会触发一些清除策略。
  • 元空间包括类的元信息,字段,静态属性,方法,常量等。

线程私有

虚拟机栈、程序计数器、本地方法栈。

  • 虚拟机栈,java方法执行的内存区域。
  • 程序计数器,存放执行指令的偏移量,行号指示器等,有用于线程中断,恢复。
  • 本地方法栈,为本地Native方法服务,深度使用操作系统的特性功能。

对象实例化

从字节码的角度分析

    1. new
      当成功加载类对象后会在堆中分配内存,从Object类开始到本类路径中的所有属性值都要分配内存。分配完成后进行零值初始化(初始化默认零值)。而在分配的过程中,实例对象引用变量占据4个字节。当指令完成后,将指向实例对象的引用变量压入虚拟机栈中。
    1. dup
      复制栈顶实例对象的引用变量,如果init方法中存在参数,还需要将参数压入到操作栈中。这是就存在两个实例对象的引用了,较前的一个引用变量作为句柄调用相关的方法,而在较后的引用变量用于赋值保存到局部变量表中。
    1. invokespecial
      调用对象实例方法,通过栈顶引用变量调用init方法clinit方法是初始化时执行的方法,而init方法是对象初始化时执行的方法。

从执行步骤的角度分析

    1. 确认类元信息。
      当JVM接受到new指令的时候,会先在metaspace元信息中检查需要创建的类元信息是否存在,如果不存在,就在双亲委派的模型下,使用当前类加载器以ClassLoader + 包名 + 类名为key进行查找对应的.class文件,找到后进行类加载,并生成对应的Class类对象
    1. 分配对象内存
      首先需要计算对象占用的空间大小,如果对象是实例对象的引用变量则只需4个字节大小。接着在堆中分配一块内存给新对象。在分配内存时需要进行同步操作(CAS失败重试、区域加锁),保证分配操作的原子性
    1. 设定默认值
      成员变量值都需要设置为默认值,即各类型的零值。
    1. 设置对象头
      设置新对象的哈希码GC信息锁信息对象所属类的元信息等等。
    1. 执行init方法
      初始化成员变量,执行实例化代码块,调用类的构造方法,并把堆内对象首地址赋值给引用变量

一个类的实例化顺序

-> 父类静态变量
-> 父类静态方法
-> 子类静态变量
-> 子类静态方法
-> 父类非静态变量(实例成员变量)
-> 父类构造方法
-> 子类非静态变量(实例成员变量)
-> 子类构造方法

垃圾回收

java会对内存进行自动分配回收管理,方便地使用内存实现程序逻辑。而不同的JVM垃圾回收,对应的堆内存划分是不同的。而垃圾回收的目的是清除不再使用的对象,自动释放内存

识别可回收对象

识别需要清除的对象,这是垃圾回收的第一步工作,判断对象是否存活,JVM引入了GC Roots。如果一个对象与GC Roots之间没有直接或间接的引用关系,那么这个对象就是可以被回收的。简单而言是一个可达性分析的实现,将一系列被称为GC Roots的变量作为初始的存活对象合集,然后从该合集出发,所有能够被该集合引用到的对象,并将其加入到该集合中,而不能被该合集所引用到的对象,并可对其宣告死亡。类静态属性中引用的对象常量引用的对象虚拟机栈中引用的对象本地方法栈中引用的对象 等等,都可以作为GC roots变量进行引用分析。

垃圾回收相关的算法

  • ‘标记-清除’,从每个GC Roots出发,依次标记有引用关系的对象,然后将没有标记的对象清除。但是这样做会产生很多的空间碎片

  • ‘标记整理算法’,也是先标记存活的对象。然后移动存活的对象到内存的一端,这就形成了一段连续的已使用空间,最后将这段内存之外的空间都清除,就形成了一片连续的内存空间,这样做就不会产生空间碎片问题。

  • ‘Mark-Copy’算法,并行的把标记和整理分成两块,垃圾回收时,只需要将存活对象复制到另一块未激活的空间中,将状态标记为激活状态,然后清除原空间中的原对象。堆空间分为较大的Eden区和两个较小的Survivor区,每次只使用Eden区和一块Survivor区,可以减少内存空间的使用。是新生代中主流的垃圾回收算法。

垃圾回收器

实现垃圾回收算法并应用再JVM环境中的内存管理模块。下面主要介绍Serial,CMS,G1三种。

  • Serial主要是引用在YGC(堆中的新生代区)中,采用串行单线程的形式完成GC任务。会在垃圾回收的某个阶段完全中断程序的执行,FGC的时间较长

  • CMS回收器Concurrent Mark Sweep Collect主要采用标记清除算法,因此会产生大量的空间碎片,对此可以在FGC后对老年代进行压缩,执行空间碎片整理。

  • G1是HotSpot在JDK7中推出的新一代垃圾回收器。G1采用Mark-Copy算法,有很好的内存整合能力,不会产生大量的空间碎片。G1会将空间分为多个区域,优先回收垃圾最多的区域。而且G1还有一大优势在于可预测的停顿时间,即能够尽可能快的在指定的时间内完成垃圾回收的任务。