JVM简述下包括什么?
一,JVM内存结构:在JVM内存结构中,大致(为什么是大致?因为虚拟机的种类繁多,有些虚拟机把栈概念合二为一,还有其他自由的实现)会分为如下的结构:
包括本地方法栈,VM栈,程序计数器,方法区,java堆,下面逐一来看下他们的功能:
①本地方法栈:放着大量虚拟机可直接调用的native方法,比如CAS模型中大量使用的Unsafe包中的方法,基本都是native方法,这些方法很多并不是用java实现的,而是C,C++等;但是可供java直接调用;
②,vm栈:存放线程执行方法时产生的栈帧,通常一个线程会有一个栈帧链,如下图所示,一个线程正在执行的栈帧只会是一个当前栈帧,栈帧中包含的数据结构包括:局部变量表(方法中的局部变量)、操作数栈(运算过程中的中间存储媒介)、动态链接、方法返回地址和一些额外的附加信息,如下图:
③,程序计数器:每个线程记录指令执行到的位置,通过程序计数器控制诸如循环,异常处理等的下一条指令;
④,方法区:存放已经被虚拟机加载的类信息,常量,静态变量,即时编译器编译后的代码等数据,还有运行时常量,通常称为永久代,通常情况不会进行GC;
⑤,java堆:绝大多数实例对象都在此存放;
换个图来看java内存模型可知:方法区和堆是线程共享的,其他的区域是线程私有的;
二,JVM GC:
1),对象是否能回收的判断:
(1),不可达性对象可以回收;
(2),对于用可达性分析法搜索不到的对象,GC并不一定会回收该对象。要完全回收一个对象,至少需要经过两次标记的过程:
第一次标记:对于一个没有其他引用的对象,筛选该对象是否有必要执行finalize()方法,如果没有执行必要,则意味可直接回收。(筛选依据:是否复写或执行过finalize()方法;因为finalize方法只能被执行一次)。
第二次标记:如果被筛选判定位有必要执行,则会放入FQueue队列,并自动创建一个低优先级的finalize线程来执行释放操作。如果在一个对象释放前被其他对象引用,则该对象会被移除FQueue队列。
根搜索算法:JVM选定诸如方法区的静态常量,本地方法中的对象等作为GC roots(对象可达树的根节点),将创建的所有的对象引用挂在树上,当对象引用从树上解挂时(没有对象再引用这个对象时),则这个对象处于不可达状态,也即是可回收状态;如下图:
2)JVM内存分区和GC算法
内存被切分为三块:新生代(刚new出来的对象),老年代(从新生代GC过来的对象或者刚new出来的大对象(直接超过了新生代的空闲内存)),永久代(方法区数据)
新生代又被分为一块Eden区和两块Survivor区;
新生代GC算法:复制算法采用的方式为从根集合进行扫描,将存活的对象移动到一块空闲的区域
标记-清除:该算法采用的方式是从跟集合开始扫描,对存活的对象进行标记,标记完毕后,再扫描整个空间中未被标记的对象,并进行清除。标记和清除的过程如下:
上图中蓝色部分是有被引用的对象,褐色部分是没有被引用的对象。在Marking阶段,需要进行全盘扫描,这个过程是比较耗时的。
清除阶段清理的是没有被引用的对象,存活的对象被保留。
标记-清除动作不需要移动对象,且仅对不存活的对象进行清理,在空间中存活对象较多的时候,效率较高,但由于只是清除,没有重新整理,因此会造成内存碎片。
标记-压缩:该算法与标记-清除算法类似,都是先对存活的对象进行标记,但是在清除后会把活的对象向左端空闲空间移动,然后再更新其引用对象的指针,如下图所示
由于进行了移动规整动作,该算法避免了标记-清除的碎片问题,但由于需要进行移动,因此成本也增加了。(该算法适用于旧生代)
3),虚拟机中GC的过程:
1,在初始阶段,新创建的对象被分配到Eden区,survivor的两块空间都为空。
2,当Eden区满了的时候,minor garbage 被触发 。
3,经过扫描与标记,存活的对象被复制到S0,不存活的对象被回收
4,在下一次的Minor GC中,Eden区的情况和上面一致,没有引用的对象被回收,存活的对象被复制到survivor区。然而在survivor区,S0的所有的数据都被复制到S1,需要注意的是,在上次minor GC过程中移动到S0中的两个对象在复制到S1后其年龄要加1。此时Eden区S0区被清空,所有存活的数据都复制到了S1区,并且S1区存在着年龄不一样的对象,过程如下图所示:
5,再下一次MinorGC则重复这个过程,这一次survivor的两个区对换,存活的对象被复制到S0,存活的对象年龄加1,Eden区和另一个survivor区被清空。
6,再经过几次Minor GC之后,当存活对象的年龄达到一个阈值之后(可通过参数配置,默认是8),就会被从年轻代Promotion到老年代。
7,随着MinorGC一次又一次的进行,不断会有新的对象被promote到老年代。
8,上面基本上覆盖了整个年轻代所有的回收过程。最终,MajorGC将会在老年代发生,老年代的空间将会被清除和压缩。
4),Minor GC,Major GC,Full GC触发条件
Minor GC:当年轻代中的Eden区满时,触发;
Major GC:清理老年代,通常由Minor GC触发;
Full GC:
(1)调用System.gc时,建议执行full GC,但是不一定执行;
(2)老年代空间不足,
(3)方法区空间不足,
(4)通过Minor GC进入老年代的平均大小大于老年代的可用内存;
(5)Minor GC触发Full GC:新生代的eden区和suvivor(使用中)向survivor(暂未使用)复制对象的时候,大于survivor(暂未使用)的内存,随即把对象转存到老年代,但同样大于老年代的可用永存
5),JVM参数与调优:
JVM参数:
-Xmx:最大允许分配堆内存;
-Xms:初始分配的堆内存; 通常与Xmx一样,避免每次GC后重新分配内存
CMSFullGCsBeforeCompaction=5:会每隔5次真正的full GC做一次压缩
-XX:CMSInitiatingOccupancyFraction=70 是指设定CMS在对内存占用率达到70%的时候开始GC(因为CMS会有浮动垃圾,所以一般都较早启动GC);
-XX:+CMSParallelRemarkEnabled 减少第二次暂停的时间,开启并行的remark;
-XX:+DisableExplicitGC 禁止代码中显式的调用GC,
-XX:+DoEscapeAnalysis 打开逃逸(例如被静态变量引用等导致无法回收)分析
XX:+UseCMSCompactAtFullCollection 开启碎片合并
XX:+UseConcMarkSweepG 使用CMS收集器
-XX:+UseParNewGC 年轻代为多线程收集。
还有更多的JAVA干货技术分享,敬请关注。。。