Java 编译解释型语言
Java 解释过程
Java 字节码
JVM结构

类加载子系统
ClassLoader 双亲委派
BootStrapClassLoader 对应目录 jre/lib
ExtClassLoader 对应目录 jre/lib/ext
AppClassLoader 对应目录 classpath
String.class.getClassLoader(); 获取加载类的加载器; 如果为空则表示是BootStrapClassLoader
运行时数据区


PC Register Program Counter
当前执行程序的地址
java方法栈

-
一个线程对应一个虚拟机栈,
-
方法开始执行进栈,执行完出站,
-
StackOutFlowError 栈溢出,方法调用层数太多
-
OutOfMemoryError 内存溢出,线程太多,内存不够创建虚拟机栈
-
-Xss设置栈大小;
栈帧

- 一个栈帧对应一个方法
堆区

JVM规范中规定所有的对象和数组都应该存放在堆中,在执行字节码指令时,会把创建的对象存放在堆中,对象对应的引用地址存放在虚拟机栈中的栈帧中,不过方法执行完之后,刚刚所创建的对象并不会立马被回收,而是要等JVM后台执行GC后,对象才会被回收。
-
Xms:ms(memory start) 指定堆的初始化内存大小,等价于-XX:InitalHeapSize
-
Xmx:mx(memory max) 指定堆的最大内存大小,等价于-XX:MaxHeapSize
-
默认情况下,初始化内存大小 = 物理内存大小/64,最大内存大小 = 物理内存大小/4;
-
一般会把-Xms 和 -Xmx设置为一样,这样 JVM 就不需要再 GC 后去修改堆的内存大小了,提高效率;
-
-XX:NewRatio 参数设置新生代和老年代的比例,默认是2,新生代占比1,老年代占2,也就是新生代占堆内存总大小1/3
-
新生代:Eden区、S0区、S1区的比例是8:1:1,-XX:SurvivorRatio
堆区对象分布
- 新创建对象放在 Eden 区,
- Eden 区满了之后进行 Young GC 之后, 把剩下的对象放入到 S0 区,并标记1
- Eden 区满了之后 S0 Young GC 之后,把 S0 区剩下的对象放入 S1 区,并标记2
- 重复 2 3 直到标记数达到 15 之后,还存活的对象放入到老年代
GC垃圾回收器

-
Young GC :负责对新生代进行垃圾回收
-
Old GC :对老年代进行垃圾回收,(目前CMS垃圾回收会单独对老年代进行垃圾回收,其他垃圾回收器基本都是整个堆回收才会回收老年代)
-
Full GC :对整个堆进行垃圾回收,包括新生代、老年代
-
新生代存放刚创建的对象;
-
老年代存放一些存活时间比较久的对象;
-
新生代首先进入Eden区,Young GC 之后存活对象会从 Eden 区放到S0区,之后GC 会从S0 放到S1区,之后跳转到S0区,来回跳转15次之后,再GC 就会进去老年代;
常见的垃圾收集器

-XX:+UseParallelGC
垃圾标记阶段
引用计数法
每个对象都保持一个引用计数器属性,用户记录对象被引用的次数
优点:
- 实现简单,计数器为0 ,表示垃圾对象
缺点:
- 需要额外的空间来存储计数,
- 需要额外的时间来维护引用计数
- 相互引用,无法处理的问题

可达性分析法(用的最多的)
以GC Roots 作为起始点,然一层层找到所引用的对象,被找到的对象就是存活对象,那么不可达对象就是垃圾对象
GC Root 的对象有哪些:(重点前面4种)
-
虚拟机栈(栈帧中的本地变量表)中的引用对象,各个线程调用方法堆栈中使用到的参数、局部变量、临时变量等
-
方法区中的类的静态属性引用的对象:java类的引用类型静态变量
-
方法区中常量引用的对象:字符串常量池的引用
-
本地方法中 jni(native方法) 引用的对象;
-
JVM内部引用的对象(class对象、异常对象NullPointException、OutOfMemoryError、类加载器)
-
所有被同步锁(syncchronized关键)持有的对象
-
JVM内部的JMCBean、JVMTI中注册的回调、本地代码缓存等
-
JVM实现中的”临时性“对象,跨代引用的对象;

GC:垃圾回收阶段
标记-清除
一种非常基础和常用的垃圾回收算法, 针对某块内存空间,比如新生代、老年代,如果可用内存不足后,就会STW,暂定用户线程执行,执行垃圾回收操作
- 标记阶段:从GC Roots开始遍历,找到可达对象,并在对象头中进行记录
- 清除阶段:堆 内存空间进行线性遍历,如果发现对象头中没有记录可达对象,则回收它
缺点:
- 效率不高,
- 空间碎片
标记-复制
利用空间换时间,需要把内存空间分为两块,每次使用一块,在进行垃圾回收时,将可达对象复制到另外没有被使用的内存块中,然后在清除当前内存块中的所有对象,后续再按照同样的流程进行垃圾回收;
可达对象比较多就不适合,需要复制量就比较大
优点 :
- 新生代适合标记复制
标记-整理
- 第一阶段:通过 GC Root 找到标记可达对象,
- 第二阶段:将所有存活的对象移动到内存的一端,整体清除
总结
分代垃圾回收算法
- 新生代中的对象存活时间比较短,那么就可以利用复制算法,它适合垃圾对象比较多的情况
- 老年代中的对象存活时间比较长,所以不太适合用复制算法,可以使用 标记-清除 、标记-整理算法,比如 CMS 使用标记-清除算法, Serial Old 垃圾收集器使用标记-整理
CMS 并行标记-清除算法:Concurrent Mark Sweep

CMS 整个垃圾回收过程变长,但是用户暂停时间STW变短,而且在垃圾回收过程中大部分时间用户线程还在执行,用户体验比较好,吞吐量变低了(单位时间内执行的用户线程变少了,被CMS GC 占用)
初始标记
- STW ,暂停所有用户线程 STW: Stop The World
- 然后标记出 GC Roots 可达对象
- 一旦标记完成,恢复所有工作线程继续执行
- 这个时间比较短
并发标记
- 并发执行,不会 STW,用户线程和 GC 线程同步执行
- 从上一个阶段标记出来的对象,开始遍历整个老年代,标记出所有的可达对象
- 比较耗时
- 并发执行,可能出现误差,所以按三色标记
重新标记
- 对上一个阶段标记的对象,进行修正,上一阶段并行
- 需要STW ,但是时间不会很长
- 增量更新
并发删除
- 删除标记的对象,不需要STW
- 由于不需要移动对象,所以和工作线程并发执行,
并发重置
CMS总结
如果在并发标记、并发清理的过程中,由于用户线程同时执行,如果有新对象进入老年代,但是空间又不够,就会造成”concurrent mode failure“,此时就会利用Serial Old来做一次垃圾收集,就会做一次全局的STW,
在并发清理的过程中,产生新的垃圾,称为”浮动垃圾“,只能等下一次 GC 的时候来清理;
由于使用标记-清除算法,所以会产生内存碎片,可以通过参数-XX:+UseCMSCompactAtFullCollection 让JVM 执行完标记-清除 之后再执行一次整理;
也可以通过-XX:CMSFullGCsBeforeCompaction 来指定多少次 GC 之后执行整理,默认是0, 每次GC之后都会整理;
G1 (Garbage First)

每一个方块叫Region,堆内存会分为2048个Region,每个Region的大小等于堆内存/2048;
还是分了Eden区、S0区、S1区、老年代,不过空间不是连续的
Humongous 区是专门用来存放大对象的,(如果一个对象的大小超过一个Region的50%,那么就是大对象)

筛选回收
- 需要STW,来清除垃圾对象,
- 可以通过-XX:MaxGCPauseMillis 来指定GC STW 停顿的时间,所以可能不会回收掉所有垃圾对象,默认是200ms
- 采用的复制算法,不会产生碎片(会把某个 Region 里的垃圾对象复制到另外空间 Region 区域,比如相邻的)
- YoungGC :Eden区满,就会触发G1的YoungGC ,对Eden区进行GC
- MixedGC :老年代的占用率达到了-XX:InitiatingHeapOccupancyPercnet 指定百分比,回收所有的新生代以及部分老年代,以及大对象区;
- FullGC : 在进行MixedGC过程中,采用的复制算法,如果复制过程中内存不够,则会触发FullGC ,会STW,并采用单线程来进行标记-整理算法进行GC,相当于用一次Serial GC