类加载器什么时候触发加载?

  1. 使用new关键字实例化对象时;
  2. 访问类的静态方法;
  3. 反射 class.forName();
  4. 初始化一个类时,发现其父类没有初始化;
  5. 虚拟机启动时,定义了main方法,那个类先初始化;

Java 文件加载到内存的过程:

1. 加载 就是把编译后的class文件,通过类加载器加载(二进制流的方式)到方法区称为DNA元数据模板	
2. 链接:

   1. 验证:魔数的校验
   2. 准备:初始化类变量(静态变量)为0值;final修饰的是在编译时初始化,这个阶段显示赋值;实例变量不在这时初始化;
   3. 解析:将常量池中的符号引用转化为直接引用;javap -v class 可以看到编译后的方法使用都是符号引用
3. 初始化:
   给类变量显示赋值
   执行类构造器方法(clinit)的过程;如果有父类会先执行父类的clinit()方法;
   虚拟机保证一个类的clinit方法被调用时被加锁,只能调用一次;

双亲委派是什么

双亲委派: 系统类加载器-》 扩展类加载器-》 引导类加载器;层层向上委派;
反向委派: rt.jar 是由引导类加载的,但是具体实现类是第三方的,是由系统类加载器加载的;

类加载器类型:

  1. Bootstrap classload(引导类加载器): 加载包名为java、javax、sun开头的

  2. 扩展类加载器:主要加载ext扩展目录下的类;

  3. application classload (系统类加载器): 负责加载classpath下的类库,一般的程序都是此加载器加载;

  4. Custom classLoad(自定义类加载器)

堆是存储的单位,栈是运行时的单位;
堆: 存储字符串常量池

编译器输入指令流分为:

基于栈的指令集架构,基于寄存器的指令集架构;

栈式架构:

1. 实现简单,适用于资源受限的系统
2. 使用零地址指令;
3. 不依赖操作系统,可移植性好,支持跨平台;

寄存器架构:

  1. 依赖硬件,可移植性差;
  2. 性能优秀,执行更少的指令

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 : 触发条件:

  1. system.gc();
  2. 老年代空间不足;
  3. 方法区(元空间)空间不足;
  4. young gc 后进入老年代的大小大于老年代可用容量大小;
  5. s1区或s0区满后,进入老年代的大小大于老年代可用容量大小;


TLAB: 堆区是线程共享的区域,任何线程都可以访问堆区;为了避免多个线程

创建对象的5个步骤

  1. 判断对象是否加载、链接、初始化、

  2. 为对象分配内存空间:

    • 内存规整; 指针碰撞
    • 内存不规整: 空闲列表分配;
  3. 处理并发安全问题

  4. 初始化分配到的空间;

  5. 设置对象的对象头信息;

  6. 执行init方法进行初始化;

finalize:

  1. 可触及
  2. 可复活: 所有的对象引用都被释放,但是对象有可能在finalize()中复活;
  3. 不可触及:

强引用(可触及): 不回收
软引用(可触及): 内存不足即回收
弱引用:一旦失去最后一个强引用,有GC时就回收
虚引用:用于跟踪对象的回收,因为创建虚引用的时候会传一个引用队列;

可达性分析算法中GC Roots的对象

  • 虚拟机栈(栈帧中的本地变量表)中引用的对象
  • 本地方法栈中的引用对象
  • 方法区中的类静态成员变量中的对象
  • 方法区中的常量引用对象

垃圾回收算法

  1. 标记阶段:

    • 引用计数算法
    • 可达性分析算法
  2. 清除阶段:

    • 复制算法(建立在存活对象少,垃圾对象多的情况: 适用于新生代; )

    • 标记-清除算法(老年代适用)

    • 标记-压缩算法 (老年代适用)

    • 扩展:

      1. 分代收集算法(分代思想):由于上面3种算法,各有优劣,但是没有一个完美的解决方案,所以提出分代(例如:新生代、老年代)分别使用不同的垃圾回收算法,提高回收效率;新生代可以用复制算法,速度快,垃圾多;老年代可以用标记-清除、标记-压缩算法,垃圾少,不需要特别快的速度;
      2. 增量收集算法:由于STW时间太长,可以让应用线程和垃圾收集线程交替执行,分阶段的收集垃圾;总的来说,增量收集算法是对现成冲突的妥善处理,允许垃圾收集线程以分阶段的方式完成标记、清理或复制工作;
      3. 分区算法:这个算法与上面的增量收集算法基本一样,主要也是降低STW的时间,主要应用于G1垃圾回收器;将一个大的内存空间分隔成一个一个小的内存空间,在规定时间内,进行最大的回收垃圾

GC算法比较

  1. 速度: 复制算法最快、标记压缩最慢
  2. 空间开销: 复制算法需要的空间最大,一般是需要复活对象的2倍;标记-压缩空间开销最小;
  3. 移动对象:标记-清除算法不需要移动对象;

垃圾收集器
serial GC(新生代)+ serial old (老年代): 串行 最小化使用内存和并行开销
Parallel GC(新生代)+ parlllel old 最大化应用程序的吞吐量
parNew(新生代)+CMS(老年代): 最小的中断和停顿时间

CMS 初始标记 、并发标记、重新标记、并发清除; 默认:使用标记-清除算法
初始标记: stw时间很短, GCroot能直接关联的对象
并发标记: 从GC root直接关联对象开始遍历整个对象图的过程;耗时长; 与用户线程并发执行
重新标记: stw时间很短 重新标记一下
并发清除: 与用户线程并发执行;此阶段清除标记阶段判断已死亡的对象,释放内存;
-XX:+UserConcMarkSweepGC 时,老年代使用CMS,新生代默认是ParNew

cms的优缺点;

  1. 并发收集;
  2. 低延迟;

缺点:

  1. 会产生内存碎片,无法分配大对象;
  2. 与用户线程并发执行阶段,会占用部分线程,总吞吐量降低;
  3. 无法清除浮动垃圾;