什么是JVM
JVM(Java Virtual Machine)是Java跨平台的基础,Java编译后的字节码可以运行在JVM上,实现一次编写到处执行
Java内存区域
程序计数器
可以看做是当前线程所执行的字节码的行号指示器
字节码解释器质性是通过改变这个计数器的值来选取下一条执行的字节码指令
Java多线程是使用线程轮流切换并分配处理器执行时间来实现
虚拟机栈
线程私有的,生命周期与线程相同,
每个方法被执行时都会创建一个栈帧,用于存储局部变量表,操作栈,动态链接和方法出口等信息
每个方法被调用直至完成的过程,就对应一个栈帧在虚拟机栈中从入栈到出栈的过程
如果线程请求的栈深度大于虚拟机允许的深度,会抛出栈溢出异常
本地方法栈
与虚拟机栈类似,虚拟机栈为虚拟机执行java方法服务,本地方法栈是为虚拟机的Native方法服务
堆
Java堆是被线程共享的一块内存区域,存放的是对象实例
是垃圾收集的主要区域
方法区
方法区也是线程的共享区域
用于存储已被虚拟机加载的类信息、常量、静态变量等
运行时常量池
常量池用于存放编译期间生成的字面值常量和符号引用
垃圾收集器GC
判断哪些需要回收
引用计数法
给对象添加一个引用计数器,每当被引用,计数器值加1;引用失效时,计数器就减1
计数器为0的对象是不能在使用的
它不能解决对象的循环引用
String a = “hello”
String b =“world”
a=b b=a 循环引用
可达性分析算法
通过名为GC Roots对象作为起始点,从起始点向下搜索,搜索走过的路径称为引用链
当一个对象到GC Roots没有任何的引用链相连,证明此对象是不可用的
可作为GC Roots的对象:虚拟机栈中的引用对象,方法区中的类静态属性引用对象,常量引用对象
Java中的引用
强引用,弱引用,软引用,虚引用
强引用:只要强引用存在,就永远不会回收掉被引用的对象
软引用:一些还有用,但是非必需的对象。被软引用关联的对象,在系统将要发生内存溢出异常之前,将把这些对象列入回收范围之内并进行二次回收
弱引用:描述非必需对象,被软引用关联的对象只能生存到下一次垃圾回收发生之前
虚引用:最弱的引用关系,
垃圾回收算法
标记-清除算法
产生大量不连续的内存碎片
复制算法
现代商业虚拟机用这个算法来回收新生代, 新生代(Copy算法)
将内存区(新生代)分为一块较大的Eden空间和两块Survivor空间, 8:1:1
回收时,将Eden和Survivor中还活着的对象一次性拷贝到另一个Survivor上,最后清理Eden和刚才用过的Survivor
保证有较大的连续空间
老生代:GC频率很低,新生代数据进入老生代的途径:1.存活次数超过15;2.发生老生代内存担保机制
新生代GC频率很快,新生代的GC叫MinorGC;老生代GC频率很低,叫做MajorGC(FullGC)
GC调优的根本目的是减少发生FullGC
标记-整理算法
标记过程与标记清除算法一致,后续步骤是先让存活的对象向一端移动,然后清理掉端边界以外的内存、
老生代使用标记整理算法
分代收集算法
根据对象的存活周期分为了新生代和老生代,根据各个年代的特点采用合适的垃圾回收算法。
新生代使用复制算法(Eden和两个Survivor 8:1:1)
老生代使用标记整理算法
垃圾收集器
存在连线,说明可以配合使用
Serial收集器
单线程的收集器,在垃圾收集时,必须暂停其他工作线程
ParNew收集器
是Serial收集器的多线程版本,使用多条线程进行垃圾收集
目前,只有他能与CMS收集器配合使用
Serial Old收集器
Serial收集器的老生代版本,也是一个单线程收集器
CMS收集器
Concurrent Mask Sweep 停顿时间最短
并发收集,低停顿
用于和用户交互频繁的网站或服务器上,这类应用注重服务器的相应速度
基于标记-清除算法实现的
整个过程分为4个步骤:
初始标记:标记GCRoots能够关联到的对象,速度很快
并发标记:此阶段允许用户线程工作
重新标记:修正并发标记期间,因用户程序运行导致标记变动的记录
并发清除:清除标记,可以和用户线程一起工作
缺点
- CPU资源敏感,并发可能会抢占CPU
- 会产生浮动垃圾
- 可能产生大量空间碎片
Parallel Scavenge收集器
关注的是垃圾收集的吞吐量,目标是达到一个可控制的吞吐量
使用复制算法
G1
基于标记-整理实现的收集器, 可以精确的控制停顿,可以实现不牺牲吞吐量的前提下完成低停顿的垃圾回收
G1将堆划分为多个大小固定的独立区域,并且跟踪区域里的垃圾堆积程度,每次优先回收垃圾最多的区域
在每个区域上有RememberSet,RememberSet机制的作用是在判断某个对象的引用链时避免整表扫描
优点:提高整堆的内存利用率,降低FullGC频次
JVM类加载机制
类加载时机
加载–>连接–>初始化
虚拟机把描述类的数据从Class文件加载到内存,并对数据进行校验,转化,解析和初始化,最终转换成可以被虚拟机直接使用的Java类型
初始化的时机
- 遇到new创建对象
- 使用反射包的方法对类进行调用时
- 初始化一个类的时候,如果发现父类没有进行过初始化,要先触发父类的初始化
- 虚拟机启动时,main方法所在的类会被初始化
类加载的过程
加载
- 通过类的全限定名获取此类的二进制字节流
- 将字节流代表的存储结构转换为方法区运行时数据结构
- 在堆中生成代表这个类的Class对象
验证
验证是连接阶段的第一步,为了确保Class文件的字节流中包含信息符合当前虚拟机的要求,不会危害虚拟机安全
文件格式验证/元数据验证/字节码验证/符号引用验证
准备
准备是正式为类变量分配内存并设置类变量初始值的阶段,
这个时候进行内存分配的仅包括类变量,不包括实例变量
解析
虚拟机将常量池内的符号引用替换为直接引用的过程
初始化
类初始化阶段是类加载的最后一步,根据程序按通过程序制定的主观计划去初始化变量和其他资源
类加载器
任意一个类,都要由加载他的类加载器和这个类本身一同确定其在Java虚拟机中的一致性
双亲委派机制
站在虚拟机的角度只有两种类加载器:启动类加载器(BootStrap由C++实现),是虚拟机自身的一部分;其他的类加载器,
细致分类:
启动类加载器
BootStrap ClassLoader负责加载放在JAVA_HOME\lib目录中的,无法被java程序引用
扩展类加载器
Extension ClassLoader负责加载在JAVA_HOME\lib\ext目录下的,开发者可以调用
应用程序类加载器
Application ClassLoader,负责加载用户路径上所指定的类库,开发者可以使用这个类加载器
双亲委派机制的工作过程:如果一个类加载器收到了类加载的请求,他不会自己去尝试加载这个类,而是把这个请求委托给父类加载器去完成,每一个层次的类加载器都是如此,所有的加载最终都应该传到顶层的启动类加载器汇总,只有当父类加载器反馈自己无法完成这个加载请求时,子加载器才会尝试自己去加载
Java内存模型
Java内存模型定义了所有的变量都存储在主内存中
每个线程有自己的工作内存,线程的工作内存中保存该线程使用到的变量的主内存副本拷贝,线程对变量的修改都在工作内存中进行,不能直接读写主存中的变量
线程间的变量传递均需要通过主内存来完成
Volatile
-
被Volatile修饰的变量在修改的时候线程之间是可见的
-
禁止重排序优化
内容摘自《深入理解Java虚拟机》,努力呀!!