一、虚拟机内存分布
一、程序计数器
是一块小的内存空间,它可以看作是当前线程所执行的字节码的行号指示器。
每条线程都需要有一个独立的程序计数器,各条线程之间计数器互相不影响,独立存储。线程正在执行一个Java方法,这个计数器记录的是正在执行的虚拟机字节码指令的地址;如果正在执行的是Native方法,这个计数器值则为空。
二、Java虚拟机栈
线程私有,用于储存局部变量表、操作数栈、动态链接、方法出口等信息。
局部/本地变量表:存放编译期可知的各种基本数据类型、对象引用(reference[参考]类型,不是对象本身,可能是指向对象的引用指针或者代表对象的句柄或其他与此对象相关的位置)
三、本地方法栈
与Java虚拟机栈发挥作用相似,区别在于虚拟机栈为虚拟机执行Java方法服务,而本地方法栈为Native方法服务。有些虚拟机直接把Java虚拟机栈和本地方法栈合二为一。
四、Java堆
线程共享,唯一目的就是存放对象实例,几乎所有的对象实例都在这里分配内存。
Java堆是垃圾回收器管理的主要区域,因此很多时候也被称为“GC堆”。
五、方法区
线程共享,用于储存已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。
运行时常量池:是方法区的一部分,用于存放编译期生成的各种字面量和符号引用,这部分将在类加载后进入方法区的运行时常量池存放。
二、GC垃圾回收器
一、如何定位垃圾
引用计数算法(ReferenceCount)
每当有一个地方引用它时,计数器值就加1;引用失效时,计数器值就减1;计数器为0的对象就是不再被使用可以被回收的垃圾。
缺点: 难以解决对象之间相互循环引用的问题。
根可达算法(RootSearching)
通过一系列的称为“GC Roots”的对象作为起始点,从这个节点开始搜索,没有搜索到的就是垃圾。
作为“GC Roots”的对象包括:栈中本地变量表中的引用对象、方法区中类静态属性引用的对象、方法区中常量引用的对象、栈中Native方法引用的对象。
二、常见得垃圾回收算法
- 标记清除(mark sweep) - 位置不连续 产生碎片 效率偏低(两遍扫描:一遍标记一遍清除)
原理:用根可达方法标记出所有能搜索到的对象,再遍历一遍将没有被标记的对象清除。 - 拷贝算法 (copying) - 没有碎片,浪费空间
复制算法的原理是:将可用内存按容量划分为大小相等的两块,每次使用其中的一块。当这一块的内存用完了,就将还存活的对象复制到另一块内存上,然后把这一块内存所有的对象一次性清理掉。 - 标记压缩(mark compact)/标记整理法 - 没有碎片,效率偏低(两遍扫描,指针需要调整)
标记清理后将内存中的对象整理到一起
三、JVM内存分代模型
- YGC回收之后,大多数的对象会被回收,活着的进入s0
- 再次YGC,活着的对象eden + s0 -> s1
- 再次YGC,eden + s1 -> s0
- 年龄足够 -> 老年代 (15 CMS 6)
- s区装不下 -> 老年代
Eden中产生的对象98%要被回收,用标记清除算法后,把对象拷入一个suvivo区中,From和To两个(suvivo区)空间垃圾回收时进行拷贝算法,一定次数后将对象移入老年代,老年代中用标记整理法。
四、常见的垃圾回收器
新生代收集器
- Serial收集器
是新生代收集的选择
单线程在进行垃圾收集时,必须暂停其他所有的工作线程,直到它收集结束(Stop The World)。 - ParNew收集器
是Serial的多线程版本 - Parallel Scavenge收集器(PS)
使用复制算法收集器
并行回收
老年代收集器
- Serial Old收集器
是Serial收集器的老年代版本,同样是一个单线程收集器,使用“标记-整理”算法。 - Parallel Old
是Parallel Scavenge收集器的老年代版本,使用多线程和“标记-整理”算法。 - CMS收集器
以获取最短回收停顿时间为目标的收集器。
包括4个部分
初始标记
并发标记
重新标记
并发清除
优点:并发收集、低停顿
缺点:1. 对CPU资源非常敏感。
2. 无法处理浮动垃圾
3. 基于“标记-清除”算法,会产生大量空间碎片