JVM 内存结构
按线程分类 一个是线程私有的、一个是线程共享的
线程私有的有 程序计数器, 本地方法栈 和 虚拟机栈。
线程共享的有 方法区, 堆。方法区是一种模型规范,具体实现的话是元空间和永久代,永久代是1.7的,1.8以后就被移除了就变成元空间了。
1.8中,方法区存在于元空间。同时,元空间不再与堆连续,而且是存放于本地内存。这意味着只要本地内存足够,它不会出现像永久代中 OutOfMemoryError 错误。
字符串常量放在堆内,类文件常量池在元空间
程序计数器为什么是私有的?
程序计数器主要有下面两个作用:
字节码解释器通过改变程序计数器来依次读取指令,从而实现代码的流程控制,如:顺序执行、选择、循环、异常处理。
在多线程的情况下,程序计数器用于记录当前线程执行的位置,从而当线程被切换回来的时候能够知道该线程上次运行到哪儿了。
需要注意的是,如果执行的是 native 方法,那么程序计数器记录的是 undefined 地址,只有执行的是 Java 代码时程序计数器记录的才是下一条指令的地址。
所以,程序计数器私有主要是为了线程切换后能恢复到正确的执行位置。
虚拟机栈和本地方法栈为什么是私有的?
虚拟机栈: 每个 Java 方法在执行的同时会创建一个栈帧用于存储局部变量表、操作数栈、常量池引用等信息。从方法调用直至执行完成的过程,就对应着一个栈帧在 Java 虚拟机栈中入栈和出栈的过程。
本地方法栈: 和虚拟机栈所发挥的作用非常相似,区别是: 虚拟机栈为虚拟机执行 Java 方法 (也就是字节码)服务,而本地方法栈则为虚拟机使用到的 Native 方法服务。 在 HotSpot 虚拟机中和 Java 虚拟机栈合二为一。
所以,为了保证线程中的局部变量不被别的线程访问到,虚拟机栈和本地方法栈是线程私有的。
一句话简单了解堆和方法区
堆和方法区是所有线程共享的资源,其中堆是进程中最大的一块内存,主要用于存放新创建的对象 (几乎所有对象都在这里分配内存),方法区主要用于存放已被加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。
年轻代内三个区 的比例 8 : 1 : 1
eden、 survivor1 和 servivor2
servivor区对象年龄大于15的时候 就会变成老年代对象, 这个参数是可以自己设置的。
gc
每个空间的执行顺序如下:
1、绝大多数刚刚被创建的对象会存放在Eden(Eden)。
2、在Eden执行第一次GC(Minor GC)之后,存活的对象被移动到其中一个Survivor(Survivor)。
3、此后,每次Eden执行GC后,存活的对象会被堆积在同一个Survivor。
4、当一个Survivor饱和,还在存活的对象会被移动到另一个Survivor。然后会清空已经饱和的哪个Survivor。
5、在以上步骤中重复N次 (N = MaxTenuringThreshold(年龄阀值设定,默认15)) 依然存活的对象,就会被移动到老年代。
从上面的步骤可以发现,两个Survivor,必须有一个是保持空的。如果两个两个Survivor都有数据,或两个空间都是空的,那一定是你的系统出现了某种错误。
我们需要重点记住的是,对象在刚刚被创建之后,是保存在Eden的(Eden)。那些长期存活的对象会经由Survivor(Survivor)转存到老年代空间(Old generation)。
也有例外出现,对于一些比较大的对象(需要分配一块比较大的连续内存空间)则直接进入到老年代。一般在Survivor 空间不足的情况下发生。
垃圾回收策略
https://blog.youkuaiyun.com/qq_44761854/article/details/123215925
标记-清理、复制、标记-整理、分代算法
软引用和弱引用的区别
弱引用与软引用的区别在于:只具有弱引用的对象拥有更短暂的生命周期。在垃圾回收器线程扫描它所管辖的内存区域的过程中,一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存。不过,由于垃圾回收器是一个优先级很低的线程, 因此不一定会很快发现那些只具有弱引用的对象。
判断一个类是无用的类
判定一个常量是否是“废弃常量”比较简单,而要判定一个类是否是“无用的类”的条件则相对苛刻许多。类需要同时满足下面 3 个条件才能算是 “无用的类” :
-
该类所有的实例都已经被回收,也就是 Java 堆中不存在该类的任何实例。
-
加载该类的 ClassLoader 已经被回收。
-
该类对应的 java.lang.Class 对象没有在任何地方被引用,无法在任何地方通过反射访问该类的方法。
虚拟机可以对满足上述 3 个条件的无用类进行回收,这里说的仅仅是“可以”,而并不是和对象一样不使用了就会必然被回收。
如何判断对象已死亡
可达性分析 与 GC Root 不可达
分代收集算法
新生代:每次收集都有大量对象死去,所以选择 标记-复制算法 只需要付出少量对象的复制成本就可以完成每次垃圾收集。
老年代:老年代的对象存活几率比较高,而且没有额外的空间对它进行分配担保,所以选择 标记-清除 或 标记-整理 算法进行垃圾收集。
垃圾收集器
CMS 收集器
从名字中的Mark Sweep这两个词可以看出,CMS 收集器是一种 “标记-清除”算法实现的,它的运作过程相比于前面几种垃圾收集器来说更加复杂一些。整个过程分为四个步骤:
初始标记: 暂停所有的其他线程(stop-the-world),并记录下直接与 GC root 相连的对象,速度很快
并发标记: 从GC Roots的直接关联对象开始遍历整个对象图的过程,这个过程耗时较长但不需要停顿用户线程
重新标记: 为了修正并发标记期间因为用户程序继续运行而导致标记产生变动的那一部分对象的标记记录,这个阶段的停顿时间一般会比初始标记阶段的时间稍长,远远比并发标记阶段时间短,这个阶段会暂停用户线程
并发清除: 开启用户线程,同时 GC 线程开始清理删除掉标记阶段判断的已经死亡的对象,由于不需要移动存活对象,所以这个阶段也是可以与用户线程同时并发的。
从它的名字就可以看出它是一款优秀的垃圾收集器,主要优点:并发收集、低停顿。但是它有下面三个明显的缺点:
-
对 CPU 资源敏感;
-
无法处理浮动垃圾;
并发标记和并发清理阶段用户线程 还在继续运行,自然不断伴随新的垃圾对象产生,这些产生的垃圾对象只能在下一次垃圾收集时清楚掉。
-
它使用的回收算法-“标记-清除”算法会导致收集结束时会有大量空间碎片产生。
G1 收集器
G1 (Garbage-First) 是一款面向服务器的垃圾收集器,主要针对配备多颗处理器及大容量内存的机器. 以极高概率满足 GC 停顿时间要求的同时,还具备高吞吐量性能特征.
被视为 JDK1.7 中 HotSpot 虚拟机的一个重要进化特征。它具备以下特点:
并行与并发:G1 能充分利用 CPU、多核环境下的硬件优势,使用多个 CPU(CPU 或者 CPU 核心)来缩短 Stop-The-World 停顿时间。部分其他收集器原本需要停顿 Java 线程执行的 GC 动作,G1 收集器仍然可以通过并发的方式让 java 程序继续执行。
分代收集:虽然 G1 可以不需要其他收集器配合就能独立管理整个 GC 堆,但是还是保留了分代的概念。
空间整合:与 CMS 的“标记-清理”算法不同,G1 从整体来看是基于“标记-整理”算法实现的收集器;从局部上来看是基于“标记-复制”算法实现的。
可预测的停顿:这是 G1 相对于 CMS 的另一个大优势,降低停顿时间是 G1 和 CMS 共同的关注点,但 G1 除了追求低停顿外,还能建立可预测的停顿时间模型,能让使用者明确指定在一个长度为 M 毫秒的时间片段内。
G1开创的基于Region的堆内存布局是它能够实现这个目标的关键。虽然G1也仍是遵循分代收集理论设计的, 但其堆内存的布局与其他收集器有非常明显的差异:G1不再坚持固定大小以及固定数量的分代区域划分, 而是把连续的Java堆划分为多个大小相等的独立区域(Region),每一个Region都可以根据需要, 扮演新生代的Eden空间、 Survivor空间, 或者老年代空间。收集器能够对扮演不同角色的Region采用不同的策略去处理, 这样无论是新创建的对象还是已经存活了一段时间、 熬过多次收集的旧对象都能获取很好的收集效果。
Region中还有一类特殊的Humongous区域, 专门用来存储大对象。G1认为只要大小超过了一个Region容量一半的对象即可判定为大对象。 每个Region的大小可以通过参数-XX:G1HeapRegionSize设定, 取值范围为1MB~32MB, 且应为2的N次幂。而对于那些超过了整个Region容量的超级大对象,将会被存放在N个连续的Humongous Region之中, G1的大多数行为都把Humongous Region作为老年代的一部分来进行看待
关于对象的内存分配,方式如下:
新分配的对象会被分配到Eden区的内存分段上,每一次年轻代的回收过程都会把Eden区存活的对象复制到Survivor区的内存分段上,把Survivor区继续存活的对象年龄加1,如果Survivor区的存活对象年龄达到某个阈值(比如15,可以设置),Survivor区的对象会被复制到Old区。复制过程是把源内存分段中所有存活的对象复制到空的目标内存分段上,复制完成后,源内存分段没有了存活对象,变成了可以使用的空的Eden内存分段了;而目标内存分段的对象都是连续存储的,没有碎片,所以复制过程可以达到内存整理的效果,减少碎片。Humongous区用于保存大对象,如果一个对象占用的空间超过内存分段的一半(比如上面的8M),则此对象将会被分配在Humongous区。如果对象的大小超过一个甚至几个分段的大小,则对象会分配在物理连续的多个Humongous分段上。Humongous对象因为占用内存较大并且连续会被优先回收。
G1 收集器的运作大致分为以下几个步骤:
-
初始标记
-
并发标记
-
最终标记
-
筛选回收
G1 收集器在后台维护了一个优先列表,每次根据允许的收集时间,优先选择回收价值最大的 Region(这也就是它的名字 Garbage-First 的由来) 。这种使用 Region 划分内存空间以及有优先级的区域回收方式,保证了 G1 收集器在有限时间内可以尽可能高的收集效率(把内存化整为零)。