JVM 内存结构
- 线程独占的
(1)程序计数器 PC
(2)虚拟机栈:线程运行需要的内存空间,其中是一个一个栈帧。栈帧是每个方法运行时需要的内存,当前正在执行的方法对应的栈帧叫做活动栈帧。
(3)本地方法栈:调用到本地方法时对应的内存空间。 - 线程共享的
(1)堆:主要存放 new 出来的对象。
(2)方法区:一种规范,逻辑上属于堆内存,但也可以不在其中。1.6实现为永久代;1.8 实现为元空间,使用系统内存。
StringTable 串池
- 位置:1.6 在永久代的常量池中(回收效率低),1.8在堆内存中
- 原理:底层通过哈希表实现,避免对相同对象的重复创建
- 字符串变量之间的拼接原理时 StringBuilder(1.8),通过 new 的方式在堆中创建对象
- 字符串常量之间的拼接在编译期间优化,对象在串池中
- 可以使用 String 的 intern() 方法,将不在串池中的对象放入串池。
(1)1.8 中,如果串池中没有此对象,则将该对象放入串池,如果有则不放入,并返回串池中的对象。
(2)1.6 中,如果串池中没有此对象,则会创建一个副本放入串池,如果有则不创建,并返回串池中的对象。
直接内存
-
特点
(1)常见于 NIO 操作时,用于数据缓冲区
(2)分配回收成本较高,但读写性能高
(3)不受 JVM 内存回收管理 -
分配和回收原理
(1)使用 Unsafe 类对其进行分配和回收,回收需要手动调用 freeMemory方法
(2)ByteBuffer 的实现类内部,使用了 Cleaner (虚引用)来监测 ByteBuffer 对象,一旦ByteBuffer 对象被垃圾回收,那么就会由 ReferenceHandler 线程通过 Cleaner 的 clean 方法调用 freeMemory 来释放直接内存
垃圾回收
如何判断对象是否应该被垃圾回收
- 引用计数法:当对象被引用的个数为 0 时,才被判定应该被回收。缺点:循环引用无法回收。
- 可达性分析(JVM 采用):判断是否可从 GCROOT 达到该对象
- 哪些对象可以作为 GCROOT ?
(1)System Class 系统核心类
(2)Native Stack 本地方法栈中的类
(3)Busy Moniter 锁对象
(4)Thread 中用到的对象
五种引用
- 强引用:由 GCROOT 直接或间接引用
- 软引用:发生 GC 之后仍然内存不足,就会释放软引用的对象。(可以关联引用队列)
- 弱引用:只要发生 GC,就会回收弱引用指向的对象。(可以关联引用队列)
- 虚引用:主要配合 ByteBuffer 用于释放直接内存。(必须关联引用队列)
- 终结器引用:用于释放对象,调用 finalize 方法。(必须关联引用队列)
当引用关联了引用队列后,一旦所引用的对象被回收,自己也会进入索引队列,等待被回收。
垃圾回收算法
- 标记清除:优点:快,不用真正删,只需将需要删除的区域设为空闲。缺点:会产生内存碎片
- 标记整理:优点:无碎片。 缺点:慢,因为需要移动。
- 复制算法:优点:快,没有碎片。缺点:需要占用双倍空间。
- 分代算法:将堆内存区域划分为新生代和老年代,新生代又划分为伊甸园和幸存区,进行分代回收。
垃圾回收器
-
串行:单线程工作,工作时需要 STW
(1)Serial:工作在新生代,使用复制算法
(2)SerialOld:工作在老年代,使用标记整理算法 -
吞吐量优先:多线程工作,工作时需要 STW,在垃圾回收时占用全部 cpu
(1)ParallelGC:工作在新生代,使用复制算法
(2)ParallelOldGC:工作在老年代,使用标记整理算法 -
响应时间优先:ConncurrentMarkSweep,简称 CMS ,并发标记清理,分为以下四个个阶段。工作在老年代
(1)初始标记:会 STW,只标记和 GCROOT 直接相连的对象,耗时很短
(2)并发标记:与用户线程一起执行,标记其他可达对象
(3)重新标记:会 STW,在并发标记的过程中,用户线程可能会改遍一些引用,所以需要重新标记。
(4)并发清理:与用户线程一起执行,使用标记清除算法
CMS 的缺点:
(1)由于使用的是标记清除算法,会产生内存碎片。
(2)在并发清理的过程中,用户线程又会产生新的垃圾,这些垃圾无法在此次垃圾回收过程中被回收,只能等待下一次,这些垃圾又被称作浮动垃圾,所以 CMS 不能等到内存不足时在执行垃圾回收,一般当内存占比超过一定阈值时就触发垃圾回收。
(3)当碎片过多或浮动垃圾过多导致内存不足,会导致并发失败,此时会将垃圾回收器退化为串行回收器,导致响应时间急剧上升。 -
G1(Garbage First)