JVM内存分为线程私有的和公共区域,前端
线程私有的由程序计数器,JAVA虚拟机栈和本地方法栈构成java
公共区域分为JAVA堆和方法区,1.8以前方法区叫作永生代,以后改成元空间算法
程序计数器:能够看做当前线程执行的字节码的行号指示器,字节码解释器就是经过改变程序计数器,选取要运行的下一条字节码。编程
程序计数器的两个功能:api
记录线程运行状态,在发生上下文切换的的时候记录程序的状态,以便恢复到原来的状态安全
控制程序的执行数据结构
程序计数器,线程私有,而且不会发生OOM并发
虚拟机栈:虚拟机栈描述方法执行的内存模型,存放局部变量表,操做数栈,动态连接和方法出口,一个方法调用到执行完成,就是栈帧在虚拟机栈中从入栈到出栈。布局
局部变量表:存在局部变量和对象引用性能
存在StackOverFlowError和OOM
本地方法栈,和JAVA虚拟机栈基本相同,可是执行的是本地方法。
存在StackOverFlowError和OOM
JAVA堆用来存放实例对象的内存区域,几乎全部对象都分配在堆
基于垃圾分代回收假说,为了更好的回收垃圾,因此须要对JAVA堆分代。
经典分代:
新生代(Eden,from survivor 和 to survivor)
老年代
其余分代:
将内存区域分为多个region,每一个region既能够做为老年代也能够做为新生代
方法区存放已经加载的类信息、常量和静态变量。
类的信息包括:版本、字段、方法、接口等 还有常量池表
1.7之后将常量和静态变量移出了
1.8之后改做元空间
方法区也存在垃圾回收:常量回收和类卸载
方法区的一部分,当类被加载后,类的常量信息存放在运行时常量池表
常量池包括:
字面量:字符串值、基本数据类型、final的常量,其余
符号引用:类和结构的名字,字段和描述符,方法名称和描述符
字面量就是值,好比int i = 1;String s =“abc”;1和“abc”就是字面量,s就是符号引用
静态常量池的概念:静态常量池就是在类没被加载的时候,.class文件中的常量池
可是常量池不是必须是编译时期产生的,好比string.intern()方法就能够将字符串对象的字面量放入字符串常量池。
存放字符串常量的地方,1.8之后在元空间
native的方法直接在JVM之外的内存区域分配内存
对象建立分五步,
类加载检查,内存分配、初始化、设置对象头、执行init方法
类加载检查:检查对象所属的类是否已经被加载了,若是被加载了,就经过常量池的符号引用在方法区找到这个类。
内存分配:为对象在JAVA堆上分配空间。指针碰撞和空闲列表,对象整理复制和清除。同步性:CAS操做失败重试或者本地线程分配缓冲
初始化:赋0值
设置对象头:对对象必要的设置,好比属于哪一个类、hash码、分代年龄还有是否启用偏向锁等
执行init,按照程序猿构造的方法,对对象初始化
对象在堆内存中,主要分为三个部分:对象头、实例数据、对齐填充
对象头:
对象自身运行数据:hashacode、分代、锁状态(Mark word记录)
对象指针:指向所属类
句柄和直接访问
句柄:JAVA栈的本地变量表中指向JAVA堆中一个句柄池中的指针,句柄池中指针指向对象实例数据和类型类型数据的指针
直接方法:JAVA栈的本地变量表中指向JAVA堆中实例对象,对象实例数据有指针指向对象类型数据
好处:直接指针,快,节省一次开销,句柄,对象移动,只须要改变句柄。hotspot用直接访问
new一个string对象会产生两个string对象,一个字符串常量在常量池一个堆中string对象
不用new直接拼接,会在堆生成一个对象,而后引用指向常量池,其实是调用了stringbuilder拼接,而后返回一个对象。
不用new直接写,在常量池,而后引用这个对象
string.intern:第一次在常量池生成一个对象的拷贝,第二次不操做
判断一个对象是否存活。
引用计数:有一个引用就+1,消失就-1,为0就死亡。hotspot没用,用的是可达性分析算法
可达性分析:从一系列GC Roots出发,按照引用关系向下搜索,完成引用链,不可达的对象就是死亡对象。
GR roots:
虚拟机栈中引用
方法区中静态属性应用的对象
常量区引用的对象
本地方法区native 方法引用的对象
锁持有的对象
虚拟机内部的对象还有类加载器
强引用:不会被回收
弱引用:不够了被回收
软引用:下一次被回收
虚引用:不影响回收
GC Roots不可达以后,进行一次标记,而后检查是否有finalize方法,有就执行一次,没有或者执行过了就能够执行回收。
finalize方法可让对象自救,好比在对象内引用上本身
同时知足:
全部实例被回收
加载器被回收
java.lang.Class对象没有被引用,不能够反射
不一样年龄段的对象之间存在指向对方的引用。
垃圾回收的时候如何处理?
使用一个记忆集:一种从非回收区域,指向回收区域的的指针的抽象集合
标记清除:效率不稳定、碎片化
标记整理:开销很大
标记复制:浪费一部份内存空间,若是按照1:1分配就会浪费50。hotspot也用的是这种,hotspot中新生代分为8:1:1,Eden和survivor,将一个Eden和survivor复制到另外一个survivor。分配不下就内存担保机制。
移动对象开销大,不移动对象分配大。从整个程序上看,移动比较划算。
碎片化问题能够经过执行一段时间以后,标记整理一次。
枚举出GC roots。如何找到引用对象?使用的是Oopmap(ordinary object pointer)普通对象指针。使用OopMap能够快速枚举gc roots,可是致使OopMap变化的指令不少,开销很大。
因此只在特定的位置,生成OopMap,这些位置叫作安全点。
安全点:因此程序只有执行到安全点的时候,才能够垃圾回收。
安全点选择:不至于等待时间太长,也不至于太频繁。因此在循环跳转,方法调用等状况下设置安全点
中断方式:抢断式和主动中断,要准备垃圾收集了就中断全部线程,不在安全点的就恢复执行到安全点,主动式设置标志位,发现标志位就主动执行到安全点中断。
安全区域:安全点选择结局了执行中线程的引用变化问题,可是对于不执行的程序,使用安全区域。线程执行到安全区域就标识一下,出区域的时候检查垃圾回收中gcroot枚举是否完成。
记忆集:一种从非回收区域,指向回收区域的的指针的抽象集合
卡表:记忆集的一种实现,定义记录进度和内存映射关系,就是记录了某一块内存中是否有对象被另外一块内存引用
字长精度和对象精度,这俩精度更高
维护卡表,保证引用关系变化的时候,卡表能够随之变化。
因为和程序是并发执行的,防止引用关系变化,使用增量更新或者初始快照。
变化致使的错误:
A引用B,而后扫描A的时候,引用被删除,因此B不在引用链,而后以后扫描过的C引用了B
增量更新:当扫过对象增长新的引用的时候,记录下来,结束后,以这些对象为root再扫一遍
初始快照:删除正在扫描对象引用的时候,记下这些对象,结束后从新扫描一遍。
CMS使用增量,G1使用快照
新生代串行收集器
标记复制
新生代并行收集器,serial并行版本
标记复制
CMS默认的新生代收集器
新生代并行收集器
标记复制
注重于可预测的停顿时间,可控制的吞吐量,对于暂停时间能够预测,吞吐量优先
经过设置最大停顿时间和和吞吐量大小的参数,调节收集新生代大小,从而决定停顿时间
还能够开启自适应调节
老年代序列收集器
标记整理
老年代并行收集器
标记整理
四个阶段:初始阶段、并发标记、从新标记、并发清除
初始阶段:GC roots枚举,停顿
并发标记:可达性分析,查找引用链
从新标记:并发标记的时候变化的引用关系从新标记,停顿
并发清除:清除
浮动垃圾和从新标记?
从新标记的是引用链里已经有的对象,可是对象的位置发生了改变,而浮动垃圾是标记过程之后产生的垃圾,不在引用链里了。
标记清除算法
缺点:浮动垃圾、资源敏感、碎片化
Garbage First 是面向局部的垃圾回收器,将内存区域分为不一样的region,1.8后支持对类的卸载。
四个阶段:初始阶段、并发标记、最终标记、筛选回收
初始阶段:GC roots枚举,借用minor gc时间完成
并发标记:可达性分析,查找引用链
最终标记:SATB中的记录,再检查一遍
筛选回收:按照能回收的空间回报回收region,直接将一个region中存活对象复制到另外一个空region,不要求一次清理干净,不影响执行就能够
SATB 原始快照算法
TAMS指针,用于并发回收的时候分配对象
缺点:内存占用过高,由于须要记忆集记录跨代引用
局部上看标记复制,整体看是标记整理
-Xms20M -Xmx20M -Xmn10M
-printGCDetials等参数运行虚拟机
证实:
大对象直接进入老年代
内存担保
动态年龄判断:相同年龄全部对象大雨survivor一半
minor gc:Eden满了
full gc:至少伴随一次minor
老年代满了
调用system.gc()可能会清理
空间担保失败
1.7以前永久代不足
CMS GC过程当中,分配新对象内存失败,直接所有暂停full gc一次
魔术和版本
常量池
访问标志
类、父类、接口索引
字段表
方法集合
属性表:属性表记录,上面三个多一些特定场景,例如字段为常量
类加载机制:虚拟机从class文件,从读取、数据检验、转换分析、初始化,最终造成能够直接使用的JAVA类的过程
类的生存周期:
加载、验证、准备、解析、初始化、使用、卸载
将.class文件加载到内存中,变成二进制流,一个class对应一个类加载器
分类:
启动类加载器,C++实现,虚拟机一部分
其余全部类加载器,java.lang.classloader子类
分类2:
启动类加载器
扩展类加载器:加载类库
应用程序类加载器:加载应用程序
一个类加载器首先将类加载请求转发到父类加载器,只有当父类加载器没法完成时才尝试本身加载。
好处:避免从新加载和核心api被篡改
加载阶段完成三件事:
将二进制流加载入内存,而且能够经过一个类全名访问
将字节流表示的静态数据结构,转化为方法区内运行时数据结构
内存中生成一个java.lang.Class对象,做为方法区中类的各类数据访问入口。
确保字节流包含的信息符合规范,不会危害虚拟机,由于二进制流不必定都是编译出来的。
为类中变量也就是静态变量分配内存。不会赋值,初始化的时候才会赋值,如今为0
1.8之后随着Class对象一块儿存放在JAVA堆
将JAVA虚拟机中常量池中符号引用,替换为直接引用的过程。
按照程序猿的编写的代码,初始化变量和其余资源。
new
反射调用reflect
子类初始化时,若是父类没有,先初始化父类
虚拟机启动,用户指定执行的主类
default
等
虚拟机字节码执行,是在线程中进行的,方法是执行的基本单元
栈帧是支持调用和执行的数据结构,执行一个方法,就将这个方法的栈帧压入虚拟机栈
每个栈帧,都包括:本地方法表、操做数栈、动态连接和方法返回地址
只有栈顶的栈帧被执行。
方法执行的过程,就是各类字节码指令往操做数栈中写入和提取的内容
栈帧中指向常量池中方法引用的调用过程。
有些类加载的时候就已经转换成直接引用了,有些是运行时才变成直接引用的,这个过程叫作动态连接。
肯定方法的版本,由于存在多态嘛,和方法执行的调用不是一个含义
解析和分派
前端编译器:编程成字节码,.java编程.class。
即时编译器:一边执行一遍编译热点代码成机器码,运行时编译器JIT just in time
提早编译器:直接编译成机器码,AOT ahead of time complier
优缺点:
即时编译消耗资源
提早编译不能跨平台
内存模型:对内存进行读写访问过程的抽象
主要目的:定义程序中各类变量的访问规则,关注虚拟机如何吧变量值存储到内存,又如何从内存中取出。
JAVA中分为主内存和工做内存,
线程在工做内存执行,变量存储在主内存,经过SAVE和LOAD操做从主内存获取数据。
lock 做用于主内存,锁定,线程独占
unlock 做用于主内存,解锁
read 做用于主内存,读出数据
load 做用于工做内存,read出来的变量放入工做内存
use 做用于工做内存,给执行引擎
assign 做用于工做内存,执行引擎操做过的值给工做内存
store 做用于工做内存,从工做内存读出,准备给主内存
write 做用于主内存,放入主内存
系统线程实现,用户线程实现和混合实现
线程安全有互斥同步和非阻塞同步
临界区、互斥量和信号量
synchronized和重入锁
区别:
重入锁能够等待可中断:得不到能够申请放弃
公平锁:按照申请顺序得到,sychronized不公平
绑定多个条件:condition条件控制阻塞
没有特殊需求,推荐synchronized,缘由:
sychronized代码易读
lock须要finally中释放,不释放永久持有
性能差很少
CAS指令的支持,完成比较和交换动做,原子性,比较变了吗,没有才执行,会有ABA问题。
乐观锁:就是不断的尝试,能够就修改,不能够就不断尝试
请求不到就等待,可是不切换,不释放资源,由于可能很快就能够得到资源了,减小切换的开销
编译的时候,没有发现访问冲突,直接消除锁
同步代码块老是过小,频繁切换会很浪费资源,因此让同步代码块的范围更大
将资源标记与属于一个线程,其余资源访问的时候,先看看有没有人占用,有的话变成传统重量级锁。
基于CAS操做,每次使用的时候CAS检查本身是否拥有。