引言
- 哪些内存需要回收?
- 什么时候回收?
- 如何回收?
说说java的引用类型?
强引用
类似Object obj = new Object()
这类的强引用,只要强引用还在,垃圾收集器永远不会回收掉被引用的对象。
弱引用
用来描述非必需对象,但是它的强度比软引用关联的对象只能生存到下一次收集发生之前。软引用用SoftRefrence
来表示。
虚引用
一个对象是否有虚引用的存在,完全不会对其生存空间造成影响,也无法通过虚引用获取一个对象实例。JDK 1.2
后用 PhantomRefrence
来表示。
弱引用
弱引用来描述非必需对象的,但是它的强度比软引用更弱一些,被弱引用关联的对象只能生存到下一次垃圾回收器之前,JDK 1.2
用 weakRefrence
来表示。
垃圾回收的目的是?
清除不再使用的引用,自动释放内存。
GC是如何判断对象是否被回收的?
GCRoot
,如果一个对象与GC Roots
之间没有直接或间接的引用关系,如:
- 某个失去任何引用的对象
- 两个互相环岛循环引用对象
判决这些对象"死缓",是可以被回收的
什么对象可以作为 GC Roots 呢?
- 类静态属性中引用的对象
- 常量引用的对象
- 虚拟机栈中引用的对象
- 本地方法栈的引用对象
说说垃圾回收的相关算法?
- 标记-清除算法
该算法从每个GC Roots
出发,依次标记有引用关系的对象,最后将没有被标记的对象清除。但是这种算法会带来大量的空间碎片,导致需要分配一块较大的连续空间的时候容易触发FGC
。
- 标记-整理算法
类似计算机的磁盘整理,首先会从GC Roots
出发标记为存活对象,然后将存活对象,然后将存活对象整理到内存空间的一端,形成连续的已使用空间,最后把已使用的空间之外的一部分全部清理掉,这样就不会产生空间碎片问题。
Mark-Copy
算法
为了能够将并行地标记和整理将空间分为两块,每次只激活其中一块,垃圾回收时只需把存活的的对象复制到另一块未激活空间上,将未激活的空间标记为已激活,将已激活空间标记为未激活,然后清除空间中的原对象。
堆内存空间分为Eden
和两块较小记为未激活,然后清除原空间的元对象。堆内存空间分为较大的Eden
和两块较小的Survivor
,每次只使用Eden
和Survivor
区的一块。这种情形下的Mark-Copy
减少内存空间的浪费。
Mark-Copy
现作为主流的YGC
算法进行新生代的垃圾回收。
说说你知道的垃圾回收器?
- Seral
Seral
回收器是一个主要用于YGC
的垃圾回收器,采用串行单线程的方式完成GC
任务,其中Stop The World
简称 STW
,即垃圾回收的某个阶段会暂停整个应用程序执行。FGC
的时间相对较长,频繁FGC
会严重影响应用程序的性能
- CMS
算法:
标记-清除算法,会产生大量的空间碎片
解决方案:
-XX: + UseCMSCommpactAtFullCollection = n
参数,在执行 n 次 FGC
后, JVM
再在老生代执行空间碎片整理,执行一次空间碎片整理,但是空间碎片整理阶段也会引发STW
。
步骤:
- 初始标记(Initital Mark)
- 并发标记(Concurrent Mark)
- 重新标记(ReMark)
- 并发清除(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个字节
- 将执向实例对象的引用变量压入虚拟机栈的栈顶
- 引用是占据存储空间的,它是一个变量值,占用4个字节
2.DUP
- 在栈顶复制该引用变量,这时的栈顶有两个指向堆内实例对象引用变量。
- 如果方法有参数,还需要把参数压入操作栈中。两个引用变量的目的不同,其中压入底下的引用用于赋值,
- 或者保存到局部变量表,另一个栈顶的引用变量作为句柄调用相关的方法。
3.INVOKESPECIAL
:
- 调用对象实例方法,通过栈顶的引用变量调用方法。是类初始化时执行的方法,而是对象初始化时执行的方法。
执行步骤角度:
1.确认类元信息是否存在:
-
当
JVM
接收到new
指令时,首先在metaspace
内检查需要创建的类元信息是否存在- 如果不存在,那么在双亲委托派模式下,使用当前类加载器以
ClassLoader
+ 包名 + 类名 为Key
进行查找对应的.class
文件- 如果没有找到,则抛出
ClassNotFoundException
- 如果找到,则进行类加载,并生成对应的
Class
对象。
- 如果没有找到,则抛出
- 如果不存在,那么在双亲委托派模式下,使用当前类加载器以
2.分配对象内存:
- 计算对象的内存占用空间
- 如果实例成员变量是引用变量,仅分配引用变量空间即可,即4个字节大小,接着在堆中划分一块内存给新对象
- 在分配内存空间时,需要进行同步操作,比如采用
CAS
失败重试,区域加锁等方式保证分配操作的原子性
3.设定默认值:
成员变量值都需要设定位默认值,即各种不同形式的0值
4.设置对象头:
设置新对象的哈希码,GC信息,锁信息,对象所属的类元信息等。这个过程的具体设置取决于JVM
实现。
5.执行init方法:
初始化成员变量,执行实例化代码块,调用类的构造方法,并把堆内对象的首地址赋值给引用变量。