01 JVM结构
PC寄存器:
- 每个线程拥有⼀个pc寄存器;
- 指向下⼀条指令的地址。
⽅法区:
- 保存装载的类的元信息:类型的常量池,字段、⽅法信息,⽅法字节码;jdk6时,String等常量信息置于⽅法区,jdk7移到了堆中;
- 通常和永久区(Perm)关联在⼀起;
堆:
- 应⽤系统对象都保存在java堆中;
- 所有线程共享java堆;
- 对分代GC来说,堆也是分代的;
栈:
- 线程私有;
- 栈由⼀系列帧组成(因此java栈也叫做帧栈);
- 帧保存⼀个⽅法的局部变量(局部变量表)、操作数栈、常量池指针;
每⼀次⽅法调⽤创建⼀个帧,并压栈。
02 JVM内存模型
- 每⼀个线程有⼀个⼯作内存,和主存独⽴;
- ⼯作内存存放主存中变量的值的拷⻉;
- 对于普通变量,⼀个线程中更新的值,不能⻢上反应在其他变量中;如果需要在其他线程中⽴即可⻅,需要使⽤volatile关键字;
- volatile不能代替锁,⼀般认为volatile⽐锁性能好(不绝对),使⽤volatile的条件是语义是否满⾜应⽤;
- 可⻅性:⼀个线程修改了变量,其他线程可以⽴即知道。
- ------------ volatile;
- ------------ synchronized(unlock之前,写变量值回主存);
- ------------ final(⼀旦初始化完成,其他线程可⻅)。
03 java四引⽤
- 强引⽤:强引⽤是使⽤最普遍的引⽤。如果⼀个对象具有强引⽤,那垃圾回收器绝不会回收它。当内存空间不⾜,Java虚拟机宁愿抛出OutOfMemoryError错误,使程序异常终⽌,也不会靠随意回收具有强引⽤的对象来解决内存不⾜的问题。
- 软引⽤:如果内存空间不⾜了,就会回收这些对象的内存。只要垃圾回收器没有回收它,软引⽤可以和⼀个引⽤队列(ReferenceQueue)联合使⽤,如果软引⽤所引⽤的对象被垃圾回收器回收,Java虚拟机就会把这个软引⽤加⼊到与之关联的引⽤队列中。
- 弱引⽤:弱引⽤与软引⽤的区别在于:只具有弱引⽤的对象拥有更短暂的⽣命周期。在垃圾回收器线程扫描它所管辖的内存区域的过程中,⼀旦发现了只具有弱引⽤的对象,不管当前内存空间⾜够与否,都会回收它的内存。弱引⽤可以和⼀个引⽤队列(ReferenceQueue)联合使⽤,如果弱引⽤所引⽤的对象被垃圾回收,Java虚拟机就会把这个弱引⽤加⼊到与之关联的引⽤队列中。
- 虚引⽤:虚引⽤在任何时候都可能被垃圾回收器回收,主要⽤来跟踪对象被垃圾回收器回收的活动,被回收时会收到⼀个系统通知。虚引⽤与软引⽤和弱引⽤的⼀个区别在于:虚引⽤必须和引⽤队列 (ReferenceQueue)联合使⽤。当垃圾回收器准备回收⼀个对象时,如果发现它还有虚引⽤,就会在回收对象的内存之前,把这个虚引⽤加⼊到与之关联的引⽤队列中。
04 GC算法分类
引⽤计数法(没有被java采⽤):
- 原理:对于⼀个对象A,只要有任何⼀个对象引⽤了A,则A的引⽤计数器就加1,当引⽤失效时,引⽤计数器就减1,只要对象A的引⽤计数器的值为0,则对象A就会被回收。
- 问题:引⽤和去引⽤伴随加法和减法,影响性能;很难处理循环引⽤。
标记清除法:
- 原理:现代垃圾回收算法的思想基础。标记-清除算法将垃圾回收分为两个阶段:标记阶段和清除阶段。⼀种可⾏的实现是,在标记节点,⾸先通过根节点,标记所有从根节点开始的可达对象。因此,未被标记的对象就是未被引⽤的垃圾对象。然后在清除阶段,清除所有未被标记的对象。
- 问题:标记和清除两个过程效率不⾼,产⽣内存碎⽚导致需要分配较⼤对象时⽆法找到⾜够的连续内存⽽需要触发⼀次GC操作。
标记压缩法:
- 原理:适合⽤于存活对象较多的场合,如⽼年代。它在标记-清除算法的基础上做了⼀些优化。标记阶段⼀样,但之后,将所有存活对象压缩到内存的⼀端。之后,清除边界外所有的空间。
- 优点: 解决了标记- 清除算法导致的内存碎⽚问题和在存活率较⾼时复制算法效率低的问题。
复制算法:
- 原理:将原有的内存空间分为两块,每次只使⽤其中⼀块,在垃圾回收时,将正在使⽤的内存中的存活对象复制到未使⽤的内存块中,之后清除正在使⽤的内存块中的所有对象,交换两个内存的⻆⾊,完成垃圾回收。
- 问题: 不适⽤于存活对象⽐较多的场合,如⽼年代。
分代回收法:
- 原理:根据对象存活周期的不同将内存划分为⼏块,⼀般是新⽣代和⽼年代,新⽣代基本采⽤复制算法,⽼年代采⽤标记整理算法。
05 MinorGC & FullGC
- Minor GC通常发⽣在新⽣代的Eden区,在这个区的对象⽣存期短,往往发⽣GC的频率较⾼,回收速度⽐较快,⼀般采⽤复制-回收算法。
- Full GC/Major GC 发⽣在⽼年代,⼀般情况下,触发⽼年代GC的时候不会触发Minor GC,所采⽤的是标记-清除算法。
06 垃圾收集器
- Serial New收集器是针对新⽣代的收集器,采⽤的是复制算法;
- Parallel New(并⾏)收集器,新⽣代采⽤复制算法,⽼年代采⽤标记整理;
- Parallel Scavenge(并⾏)收集器,针对新⽣代,采⽤复制收集算法;
- Serial Old(串⾏)收集器,新⽣代采⽤复制,⽼年代采⽤标记清理;
- Parallel Old(并⾏)收集器,针对⽼年代,标记整理;
- CMS收集器,基于标记清理;
- G1收集器(JDK):整体上是基于标记清理,局部采⽤复制;
综上:新⽣代基本采⽤复制算法,⽼年代采⽤标记整理算法,cms采⽤标记清理;
07 Java类加载机制
概念:
- 虚拟机把描述类的数据⽂件(字节码)加载到内存,并对数据进⾏验证、准备、解析以及类初始化,最终形成可以被虚拟机直接使⽤的java类型(java.lang.Class对象)。
类⽣命周期:
- 类加载过程:读取⼆进制字节流到jvm—>验证格式语义等—>为静态变量分配内存空间—>常量池引⽤解析—>执⾏static标识的代码。
- 加载过程:通过⼀个类的全限定名来获取定义此类的⼆进制字节流,将这个字节流所代表的静态存储结构转化为⽅法区的运⾏时数据结构。在内存中(⽅法区)⽣成⼀个代表这个类的java.lang.Class对象,作为⽅法区这个类的各种数据的访问⼊⼝;
- 验证过程:为了确保Class⽂件的字节流中包含的信息符合当前虚拟机的要求,⽂件格式验证、元数据验证、字节码验证、符号引⽤验证;
- 准备过程:正式为类属性分配内存并设置类属性初始值的阶段,这些内存都将在⽅法区中进⾏分配;
准备阶段,static对象会被设置默认值,static final对象会被赋上给予的值。
- 解析阶段:虚拟机将常量池内的符号引⽤替换为直接引⽤的过程。
i. 符号引⽤:字符串,引⽤对象不⼀定被加载;
ii. 直接引⽤:指针或者地址偏移量,引⽤对象⼀定在内存中。
- 初始化阶段:类初始化阶段是类加载过程的最后⼀步。初始化阶段就是执⾏类构造
<clint>()
⽅法的过程。 - 使⽤阶段:
- 卸载阶段:
08 类加载器
java默认提供三个类加载器:
- BootStrap ClassLoader 启动ClassLoader(sun.boot.class.path):最顶层的加载类,主要加载jdk中的核⼼库,%JRE_HOME%\lib下的rt.jar、resources.jar、charsets.jar和class等。BootstrapClassLoader不继承⾃ClassLoader,因为它不是⼀个普通的Java类,底层由C++编写,已嵌⼊到了JVM内核当中,当JVM启动后,Bootstrap ClassLoader也随着启动,负责加载完核⼼类库后,并构造Extension ClassLoader和App ClassLoader类加载器。
- Extension ClassLoader 扩展ClassLoader(java.ext.dirs):扩展的类加载器,加载⽬录%JRE_HOME%\lib\ext⽬录下的jar包和class⽂件。还可以加载-D java.ext.dirs选项指定的⽬录。
- App ClassLoader 应⽤ClassLoader/系统ClassLoader(java.class.path):也称为SystemAppClass加载当前应⽤的classpath的所有类。 除了BootStrap ClassLoader,每个ClassLoader都有⼀个Parent作为⽗亲。
双亲委派机制:
- 定义:当⼀个ClassLoader实例需要加载某个类时,它会试图亲⾃搜索某个类之前,先把这个任务委托给它的⽗类加载器,这个过程是由上⾄下依次检查的,⾸先由最顶层的类加载器Bootstrap ClassLoader试图加载,如果没加载到,则把任务转交给Extension ClassLoader试图加载,如果也没加载到,则转交给App ClassLoader 进⾏加载,如果它也没有加载得到的话,则返回给委托的发起者,由它到指定的⽂件系统或⽹络等URL中加载该类。如果它们都没有加载到这个类时,则抛出ClassNotFoundException异常。否则将这个找到的类⽣成⼀个类的定义,并将它加载到内存当中,最后返回这个类在内存中的Class实例对象。
- 作⽤:避免重复加载;考虑到安全因素,避免⾃定义的类去替代系统类,如String。
- jvm如何判定两个class是否相同?JVM在判定两个class是否相同时,不仅要判断两个类名是否相同,⽽且要判断是否由同⼀个类加载器实例加载的。只有两者同时满⾜的情况下,JVM才认为这两个class是相同的。
- ⾃底向上检查类是否已经加载;
- ⾃顶向下尝试加载类。
custom classloader:⾃定义classloader
a. Java中提供的默认ClassLoader,只加载指定⽬录下的jar和class,如果我们想加载其它位置的类或jar时,就需要定义⾃⼰的ClassLoader。
b. 步骤:
- 继承java.lang.ClassLoader
- 重写⽗类的findClass⽅法
09 引起类加载的五个⾏为
- 遇到new、getstatic、putstatic或invokestatic这四条字节码指令
- 反射调⽤的时候,如果类没有进⾏过初始化,则需要先触发其初始化
- ⼦类初始化的时候,如果其⽗类还没初始化,则需先触发其⽗类的初始化
- 虚拟机执⾏主类的时候(有 main(string[] args))
- JDK1.7 动态语⾔⽀持