1、运行时数据区域
一、程序计数器:
- 当前线程执行字节码的行号指示器(经过改变计数器的值来选择下条须要执行的字节码指令)
- 每一个线程有独立的程序计数器(线程私有,为了切换线程时能恢复到挣钱的执行位置)
- 若是执行java方法,计数器记录正在执行的字节码指令地址。若是执行的是Native方法,计数器为空。
- 惟一没规定任何OutOfMemoryError状况的区域。
二、虚拟机栈
- 为执行Java方法服务
- 线程私有,声明周期跟线程一致
- 一个Java方法执行到结束的过程:栈帧从入栈到出栈的过程
- 栈帧存储局部变量表(包括基本数据类型和对象的引用类型)、操做栈、动态连接、方法出口等信息
- 异常:线程请求的栈深度大于虚拟机容许的深度,抛出StackOverflowError。虚拟机动态扩展过程当中没法申请到足够的内存,会抛出OutOfMemoryError异常。
三、本地方法栈
- 为虚拟机用到的Native方法服务
- 也会抛出StackOverflowError和OutOfMemoryError的异常
四、Java堆
- 用来存储对象的实例
- 全部线程共享的一块内存区域
- 从内存回收的角度能够分为新生代和老年代
五、方法区
- 存放被虚拟机加载的类信息、常量、静态变量等
- 线程共享
六、运行时常量池
- 方法区的一部分
- 存放编译期生成的各类字面量和符号引用
2、垃圾回收(GC)
一、判断对象是否存活
一、引用计数法:java
- 给Java对象添加一个引用计数器,每当有一个地方引用它时,计数器+1;引用失效则-1。当计算器不为0时,判断对象存活
- 缺点:若是两个对象相互循环引用时,由于计算器不为0,不能被回收。实际上对象应该被回收。
二、可达性分析算法:算法
(1)原理:
把"GC Roots"的对象做为起点,而后向下搜索,搜索所走过的路径称为引用链,当一个对象到GC Roots没有任何引用链相连时,即该对象不可达,也就说明此对象是不可用的。
(2)可做为GC Root对象:bash
- 虚拟机栈(栈帧中的本地变量表)中引用的对象
- 方法区中类静态属性引用的对象
- 方法区常量引用的对象
- 本地方法栈中JNI引用的镀锡
三、判断一个类可回收:数据结构
- 该类全部实例对象被回收
- 加载该类的ClassLoader已经被回收
- 该类对应的Class对象没被引用,没法经过反射访问该类的方法
二、引用类型
经过引用的强度分为强、软、弱、虚四种引用类型ui
- 强应用:通常Object b=new Object()这类引用,只要强引用存在,GC就不会回收被引用的对象。
- 软引用:系统在发生内存溢出以前,会将这些对象二次回收。若是还没足够内存,才会抛出内存溢出异常。经过SoftReference来实现。
- 弱引用:GC回收时,不管内存是否足够,都会收会被弱引用关联的对象。经过WeakReference来实现。
- 虚引用:做用是在对象被回收时收到一个系统通知。经过PhantomReference来实现。
三、垃圾收集算法
一、标记-清除算法:标记出全部须要回收的对象,标记完统一清除被标记的对象。spa
缺点:线程
- 标记和清理的效率不高
- 标记清除后会产生大量不连续的内存碎片
二、复制算法:将内存分为大小相等的两块,每次只用一块,当这一块内存用完,将存活对象复制到另外一块,将使用过的内存一次清理。指针
优缺点:code
- 不会产生内存碎片的问题
- 缺点是将内存缩小到了以前的一半
- 在对象存活率高时进行屡次复制操做,效率会低。
三、标记-整理算法:标记须要回收的对象,将存活对象向一端移动(整理),清理掉可回收的对象。cdn
四、分代收集算法:根据对象存活周期不一样,将Java堆内存分为新生代和老年代。
- 新生代:只有少许对象存活,使用复制算法。
- 老年代:大量对象存活,使用标记清除或者标记整理算法。
3、类加载机制
一、类加载时机
一、定义:
把Class文件加载到内存中,并对数据进行校验、解析和初始化,行成可被虚拟机直接使用的Java类型。类从被加载到虚拟机内存中开始,到卸载出内存结束。
二、生命周期:
- 加载
- 验证
- 准备
- 解析
- 初始化
- 使用
- 卸载 加载、验证、准备、初始化、卸载的顺序肯定。
三、须要对类进行初始化的场景
- new实例化对象、读取或设置类的静态字段,调用类的静态方法(被final修饰,已在编译期将结果放入常量池的静态字段除外)
- 对类进行反射
- 初始化一个类,若父类还没初始化,先触发父类的初始化
- 需指定一个执行的主类(包含main方法的类),虚拟机先初始化该类
- JDK1.7动态语言,MethodHandle实例解析结果REF_getStatis、REF_putStatis、REF_invokeStatis的方法句柄
四、不会方法初始化的场景:
全部引用类的方式不会触发初始化,例如子类引用父类的静态字段,只触发父类初始化。
五、接口初始化和类初始化的区别:
接口初始化时,不要求其父类接口所有完成初始化。
二、类加载过程
包括加载、验证、准备、解析、初始化5步
一、加载:
- 经过类全限定名获取定义该类的二进制字节流
- 将字节流的静态存储结构转换为方法区的运行时数据结构
- 内存中生成该类的Class对象,做为访问该类数据的入口
二、验证:
- 文件格式验证,验证字节流是否符合Class文件格式规范
- 元数据验证,对字节码描述的信息进行语义分析
- 字节码验证,肯定语义合法
- 符号引用验证,对常量池符号引用校验
三、准备: 为类变量(static修饰的变量)分配内存并设置变量初始值
四、解析: 将常量池符号引用替换为直接引用(直接指向目标的指针)的过程
五、初始化: 开始执行类中定义的Java代码
三、类加载器
同一个Class文件,被两个不一样的类加载器加载,这两个类不相等。相等包括equals、instanceOf、isInstance方法返回的结果。
一、类别:
- 启动类加载器(Bootstrap ClassLoader):加载<JAVA_HOME>\lib目标,或者被-Xbootclasspath参数指定的路径,可被虚拟机识别的类库
- 扩展类加载器(Extension ClassLoader):加载<JAVA_HOME>\lib\ext目录,或被java.ext.dirs系统变量指定的路径的类库
- 应用类加载器(Application ClassLoader):加载ClassPath上指定的类库
二、双亲委托机制
除了顶层的类加载器外,其余的类加载器都有本身的父类加载器。父子之间经过组合来复用父加载器代码。
双亲委托机制的工做流程:一个类加载器收到类加载的请求,首先将请求委托给父类加载器去完成,最终全部加载请求都会传递给顶层的启动加载器中。当父加载器发现未找到所需的类而没法完成加载请求时,子加载器才尝试去加载。
ClassLoader
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException {
//检查请求的类是否已经被加载
Class<?> c = findLoadedClass(name);
if (c == null) {
try {
if (parent != null) {
//让父类加载器去尝试加载
c = parent.loadClass(name, false);
} else {
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
//父类加载器抛异常
}
if (c == null) {
//而后调用自身的findClass方法来进行类加载
c = findClass(name);
}
}
return c;
}
复制代码
- 先检查是否被加载过,若是没有则调用父加载类去加载
- 父加载器为空,则调用启动类加载器
- 父加载器加载失败,则抛出ClassNotFoundException异常
- 而后去调用自身的findClass方法去进行类加载