1、为什么需要虚拟机
http://baijiahao.baidu.com/s?id=1594810966058734748&wfr=spider&for=pc
2、虚拟机体系架构
https://blog.csdn.net/u013256816/article/details/51484031
https://blog.csdn.net/zhangjg_blog/article/details/20380971
3、虚拟机内存区域
- 程序计数器:当前线程执行的字节码的行号指示器。
- 栈:栈是线程私有的,每创建一个线程,虚拟机就会为这个线程创建一个虚拟机栈,虚拟机栈表示Java方法执行的内存模型,每调用一个方法就会为每个方法生成一个栈帧(Stack Frame),用来存储局部变量表、操作数栈、动态链接、方法出口等信息。每个方法被调用和完成的过程,都对应一个栈帧从虚拟机栈上入栈和出栈的过程。虚拟机栈的生命周期和线程是相同的”。
本地栈: - 堆:存放对象
- 方法区:存放类信息(class文件种的常量池)、静态变量(static修饰)、常量(final修饰)
4、垃圾回收机制
垃圾回收一般必须完成两件事:检测出垃圾;回收垃圾。
- 检测垃圾的算法:
(1):引用计数法:给对象增加添加一个引用计数器,有一个地方引用,计数值就加1;引用失效则减1;计数器为0说明可以垃圾回收。
缺点:无法解决相互引用的问题
(2) : 可达性分析算法:
以一系列"GC Roots"对象为起点,判断别的对象与他们是否存在引用链关系;没有则说明该对象可被回收。
可作为GC Roots的对象:
(1):虚拟机栈(栈帧中的本地变量表)中引用的对象
(2):方法区中类静态属性引用的对象
(3):方法区中常量引用的对象
(4):本地方法栈(native方法)引用的对象 - 垃圾回收算法
(1):标记——清除算法
缺点:标记清除产生大量不连续的碎片空间,导致分配大对象无法找到足够的内存提前触发垃圾回收。
(2):复制算法
年轻代垃圾回收采用这种算法,把年轻代分为一个Eden区,2个Survivor区(from和to)。
大小比例是:Eden:from:to = 8 :1:1。
对象在Eden和from中出生,垃圾回收后还存在放在to中,to不足则需要老年代担保。
缺点:对象存活率高时,复制效率变低;需要别的空间进行担保,以应对所有对象存活的极端情况。
(3):标记——整理算法
标记后存活对象移向一端,老年代采用的GC算法。
(4):分代收集算法
为什么新生代内存需要有两个Survivor区 - java中的垃圾回收器:
现在java虚拟机垃圾回收采用的分代收集算法,因此年轻代和老年代有各自的垃圾回收器。
Serial收集器:年轻代的单线程收集器,垃圾回收时必须暂停所有的工作线程。
ParNew收集器:Serial收集器的多线程版。
Serial适合运行在单CPU,ParNew适合运行在多CPU下,目前这有这2个收集器适合与CMS收集器配合。
CMS收集器:Concurrent Mark Sweep,由名字可以看出基于“标记——清除”算法实现,是一种以获取最少停顿时间为目标的收集器。
CMS如何减少时间停顿? - CMS垃圾回收分为4个过程:
1初始标记:只标记老年代GC roots“根对象” 和年轻代中对老年代的可达对象,速度很快,会停止其他线程
2并发标记:根据初始标记得到的存活对象,递归其引用链,标记其他可达对象。因为该阶段是并发的,标记过程中工作线程也在运行,就有可能出现新生代对象晋升到老年代或者直接在老年代中中分配对象。为了提高重新标记的效率,该阶段会把上述对象所在的Card标识为Dirty,后续只需扫描这些Dirty Card的对象,避免扫描整个老年代。
3重新标记:重新对GC roots 、Dirty Card等对象进行标记,该过程会暂停工作线程
4并发整理:清理垃圾
CMS的优缺点: - 优点:并发收集,减少停顿
- 缺点:
1基于标记——清除算法,会产生大量的空间碎片。还好CMS可以设置参数对内存碎片进行合并整理。
2CMS在并发整理阶段客户线程还在运行,于是得预留空间给客户线程使用,当老年代空间达到92%就会触发一次Full gc。
总的来说:CMS回收器减少了回收的停顿时间,但是降低了堆空间的利用率。
https://www.jianshu.com/p/2a1b2f17d3e4
https://blog.csdn.net/wfh6732/article/details/57490195?utm_source=itdadao&utm_medium=referral
5、 内存分配机制
1、大多数情况下,对象在新生代Eden区分配,Eden区空间不足时触发minor GC.
2、大对象直接在老年代分配空间。典型的大对象是很长的字符串和数组,虚拟机中有一个参数,空间大于这个参数的对象直接在老年代分配。
3、在Eden出生的对象经过一次minor GC后仍然存在,且能被survivor分区容纳,年龄就加1,当年龄增加到15(值可以设置)就会晋升到老年代。
4、动态年龄判断:不是必须达到某个年龄才能晋升到老年代,相同年龄所有对象总和大于survivor50%,可以直接进入老年代。
5、空间担保分配:进行Minor GC之前,需要检查老年代最大连续空间是否大于所有新生对象总和,如果小于,看看虚拟机是否允许担保失败,允许的话继续检查老年代最大的连续可用空间是否大于历次晋升到老年区的平均值,大于的话可以进行Minor GC,小于的话或者不允许是担保失败则要进行一次Full GC。
6、类加载过程
- 类加载过程:加载——验证——准备——解析——初始化
1、加载
通过一个类的全限定名来获取该类的二进制字节流,对数据转换后存储在方法区,在方法区生成一个代表该类的class对象,作为数据的访问入口。
需要注意的是:二进制字节流不一定要从class中获取,可以从jar包、网络、或者动态代理生成的代理类。
2、验证:确保Class文件的字节流符合虚拟机的要求
- 文件格式验证:验证字节流是否符合Class文件格式的规范,例如是否以魔数开头,主次版本号是否在当前虚拟机的处理范围内,常量池的常量是否符合要求等,通过这个阶段验证后字节流才会进入方法区中存储。
- 元数据验证:对字节码描述的信息进行语义分析,保证符合Java语言规范,例如:验证该类是否继承了不被允许继承的类(final),是否实现了接口的所有方法等。
- 字节码验证:对类的方法体进行校验分析,例如:方法中的类型转换是否有效等
- 符号引用验证: 对类自身以外的信息进行验证,例如:是否能够通过全限定名找到对应的类等
3、准备:为类变量(不包括实例变量)在方法区分配内存,并设置初始值。
4、解析:将常量池内的符号引用替换为直接引用。
5、初始化:执行静态代码块,为静态变量赋值,该过程是线程安全的,事饿汉式单例的基础。
7、类加载器
java有四种类加载器,启动类加载器(Bootstap ClassLoader),扩展类加载器(Extension ClassLoader),应用程序类加载器(Application ClassLoader),也称为系统加载器,应用程序一般是由这3种类加载器相互配合加载,每种加载器会加载特定路径下的源码。
双亲委派模型的工作过程:一个类加载器收到类加载的请求,他首先不会自己区加载,而是把委托其父类加载器去加载,最终都会让启动类加载器去尝试加载,只有当父加载器无法完成这个加载请求(在他的搜索范围没有找到这个类),子加载器才会尝试自己去加载。
双亲委派模型的好处:避免加载到跟系统中定义的同名的类,造成系统的混乱。
8 四种引用
- 强引用
赋值符号即为强引用,例如: Object o1 = new Object();
或者o1 = o2,当内存空间不足时,虚拟机即使抛出内存溢出停止程序也不会回收强引用对象,因此在使用过程中,如果是全局变量,不用的时候可以置为null,下次垃圾回收内存就会被回收。 - 软引用
内存空间足够时,垃圾回收时不会回收;内存空间不足时,垃圾回收时会回收软引用的内存。主要用于避免内存溢出。 - 弱引用
垃圾回收时不管内存空间是否足够均会回收。主要用于避免内存泄漏。(Handler内存溢出的解决方式) - 虚引用