JVM从操作系统申请到内存,然后将其划分为以下区域


加载java.lang.String的过程
a. 程序启动,进入ApplicationClassLoader
b. ApplicationClassLoader 检查他的父类加载器是否加载过了,没有则先加载
c. ExtensionClassLoader 检查他的父类加载器是否加载过了,没有则先加载
d. BootStrapClassLoader发现没有父类加载器,就自己扫描自己的目录,找到java.lang.String这个类,直接由BootStrapClassLoader负责后续的加载过程,查找环节结束
加载自定义的类 Animal
a. 程序启动,进入ApplicationClassLoader
b. ApplicationClassLoader 检查他的父类加载器是否加载过了,没有则先加载
c. ExtensionClassLoader 检查他的父类加载器是否加载过了,没有则先加载
d. BootStrapClassLoader发现没有父类加载器,就自己扫描自己的目录,未找到,则回到子类加载器
e. ExtensionClassLoader 扫描自己的目录,未找到,则回到子类加载器
f. ApplicationClassLoader 扫描自己的目录,找到该类,直接由其负责后续的加载过程,查找环节结束(如果也没找到,则会抛出类找不到的异常)

垃圾回收主要是回收的是 堆 上的实例对象
垃圾的判定:

当此处的引用计数为0时,就认为该对象是垃圾。
void func(){
Test t = new Test();
Test t2 = t;
}
当该方法执行完毕后,t 和 t2 跟着栈帧一起释放,对应的引用实例对象的引用计数就 -1
缺点:

假设再加上 a1.a = a2; a2.a = a1;

接下来执行 a1 = null ; a2 = null;

此时两个对象的引用计数为0,无法释放,外界也没有代码访问这两个对象,这就造成了 “内存泄漏” 问题(申请了内存却没有释放)

垃圾的回收:
标记 - 清除
标记:就是对对象进行可达性分析,如果是垃圾则标记为垃圾

清除:这里的垃圾都是不连续的,当释放掉这些资源后,会产生很多的 内存碎片 ,内存碎片过多,则空间利用率会下降(比如内存碎片加起来有500M,要申请200M的内存可能会申请失败,因为可能没有连续的200M的内存)

复制算法
为了解决上述的内存碎片的问题, 又引入了复制算法

将不是垃圾的内存中的数据,拷贝到没有使用的另一半内存中,然后将之前的一半空间完全释放掉

缺点:
标记 - 整理
类似于顺序表的删除操作,将要保留的前移,然后释放掉后半部分的空间


实际JVM是将多种方案结合 – 分代回收 (根据对象的“年龄”分类,熬过一轮GC扫描,年龄就大了一岁)

垃圾回收器
Serial收集器、Serial Old收集器: 串行收集,产生严重的STW
ParNew收集器、Parallel Old收集器、Parallel Scavenge: 并发收集,引入多线程扫描
CMS收集器: 尽可能使得STW时间短1
1. 初始标记,速度很快,会引起短暂的STW
2. 并发标记,速度慢,但可以和业务线程并发执行
3. 重新标记,针对2进行微调
4. 回收内存,并发
G1收集器: 将整个内存划分为很多个小的区域,一次扫描若干个区域(分多次扫描)