类加载器什么时候触发加载?
- 使用new关键字实例化对象时;
- 访问类的静态方法;
- 反射 class.forName();
- 初始化一个类时,发现其父类没有初始化;
- 虚拟机启动时,定义了main方法,那个类先初始化;
Java 文件加载到内存的过程:
1. 加载 就是把编译后的class文件,通过类加载器加载(二进制流的方式)到方法区称为DNA元数据模板 2. 链接: 1. 验证:魔数的校验 2. 准备:初始化类变量(静态变量)为0值;final修饰的是在编译时初始化,这个阶段显示赋值;实例变量不在这时初始化; 3. 解析:将常量池中的符号引用转化为直接引用;javap -v class 可以看到编译后的方法使用都是符号引用 3. 初始化: 给类变量显示赋值 执行类构造器方法(clinit)的过程;如果有父类会先执行父类的clinit()方法; 虚拟机保证一个类的clinit方法被调用时被加锁,只能调用一次;
双亲委派是什么
双亲委派: 系统类加载器-》 扩展类加载器-》 引导类加载器;层层向上委派;
反向委派: rt.jar 是由引导类加载的,但是具体实现类是第三方的,是由系统类加载器加载的;类加载器类型:
Bootstrap classload(引导类加载器): 加载包名为java、javax、sun开头的
扩展类加载器:主要加载ext扩展目录下的类;
application classload (系统类加载器): 负责加载classpath下的类库,一般的程序都是此加载器加载;
Custom classLoad(自定义类加载器)
堆是存储的单位,栈是运行时的单位;
堆: 存储字符串常量池
编译器输入指令流分为:
基于栈的指令集架构,基于寄存器的指令集架构;
栈式架构:
1. 实现简单,适用于资源受限的系统 2. 使用零地址指令; 3. 不依赖操作系统,可移植性好,支持跨平台;
寄存器架构:
- 依赖硬件,可移植性差;
- 性能优秀,执行更少的指令
java的指令都是基于栈进行设计的;
虚拟机栈: 存在oom(内存溢出),不存在GC,
1. 局部变量表: 基本单位是slot
2. 操作数栈:(tip: 局部变量使用时,必须显示赋值;)
1. 操作数栈是jvm执行引擎的一个工作区,当一个方法刚开始执行的时候,一个新的栈帧也会随之被创建,这时操作数栈是空的;
2. 操作数栈都会拥有一个明确的栈深度用于存储数值,最大的深度在编译器就定义好了;javap可看到;
3. 动态链接: 这里只是指方法层面的把符号引用转化为直接引用;
4. 方法返回地址
5. 附加信息
方法区(元空间):
包含:
- 类型信息(接口、枚举等信息)、
- 域信息(public、private、projected…)、
- 方法信息(方法名称、方法返回值…)、
- 运行时常量池(就是javap 中看到的常量池,在运行时加载到内存中,就叫运行时常量池)
内存泄漏:是由程序导致分配的内存没有被正确回收导致;通常需要排查代码;
内存溢出:通常是可分配的内存超过了总的内存大小,内存泄漏最终也可能导致内存溢出;通过设置jvm堆内存大小xms、xmx
**Minor GC(年轻代)**: 会触发STW,暂停其他用户的线程,等待垃圾收集结束,用户线程才恢复运行;
Major GC(老年代): 会触发STW,执行major GC前会先执行minor GC;
Full GC : 触发条件:
- system.gc();
- 老年代空间不足;
- 方法区(元空间)空间不足;
- young gc 后进入老年代的大小大于老年代可用容量大小;
- s1区或s0区满后,进入老年代的大小大于老年代可用容量大小;
堆:
TLAB: 堆区是线程共享的区域,任何线程都可以访问堆区;为了避免多个线程
创建对象的5个步骤:
判断对象是否加载、链接、初始化、
为对象分配内存空间:
- 内存规整; 指针碰撞
- 内存不规整: 空闲列表分配;
处理并发安全问题
初始化分配到的空间;
设置对象的对象头信息;
执行init方法进行初始化;
finalize:
- 可触及
- 可复活: 所有的对象引用都被释放,但是对象有可能在finalize()中复活;
- 不可触及:
强引用(可触及): 不回收
软引用(可触及): 内存不足即回收
弱引用:一旦失去最后一个强引用,有GC时就回收
虚引用:用于跟踪对象的回收,因为创建虚引用的时候会传一个引用队列;
可达性分析算法中GC Roots的对象:
- 虚拟机栈(栈帧中的本地变量表)中引用的对象
- 本地方法栈中的引用对象
- 方法区中的类静态成员变量中的对象
- 方法区中的常量引用对象
垃圾回收算法:
标记阶段:
- 引用计数算法
- 可达性分析算法
清除阶段:
复制算法(建立在存活对象少,垃圾对象多的情况: 适用于新生代; )
标记-清除算法(老年代适用)
标记-压缩算法 (老年代适用)
扩展:
- 分代收集算法(分代思想):由于上面3种算法,各有优劣,但是没有一个完美的解决方案,所以提出分代(例如:新生代、老年代)分别使用不同的垃圾回收算法,提高回收效率;新生代可以用复制算法,速度快,垃圾多;老年代可以用标记-清除、标记-压缩算法,垃圾少,不需要特别快的速度;
- 增量收集算法:由于STW时间太长,可以让应用线程和垃圾收集线程交替执行,分阶段的收集垃圾;总的来说,增量收集算法是对现成冲突的妥善处理,允许垃圾收集线程以分阶段的方式完成标记、清理或复制工作;
- 分区算法:这个算法与上面的增量收集算法基本一样,主要也是降低STW的时间,主要应用于G1垃圾回收器;将一个大的内存空间分隔成一个一个小的内存空间,在规定时间内,进行最大的回收垃圾
GC算法比较:
- 速度: 复制算法最快、标记压缩最慢
- 空间开销: 复制算法需要的空间最大,一般是需要复活对象的2倍;标记-压缩空间开销最小;
- 移动对象:标记-清除算法不需要移动对象;
垃圾收集器:
serial GC(新生代)+ serial old (老年代): 串行 最小化使用内存和并行开销
Parallel GC(新生代)+ parlllel old 最大化应用程序的吞吐量
parNew(新生代)+CMS(老年代): 最小的中断和停顿时间
CMS 初始标记 、并发标记、重新标记、并发清除; 默认:使用标记-清除算法
初始标记: stw时间很短, GCroot能直接关联的对象
并发标记: 从GC root直接关联对象开始遍历整个对象图的过程;耗时长; 与用户线程并发执行
重新标记: stw时间很短 重新标记一下
并发清除: 与用户线程并发执行;此阶段清除标记阶段判断已死亡的对象,释放内存;
-XX:+UserConcMarkSweepGC 时,老年代使用CMS,新生代默认是ParNew
cms的优缺点;
- 并发收集;
- 低延迟;
缺点:
- 会产生内存碎片,无法分配大对象;
- 与用户线程并发执行阶段,会占用部分线程,总吞吐量降低;
- 无法清除浮动垃圾;