深入理解Java虚拟机高度总结

JVM
一. 自动内存管理机制

1.1 程序计数器是一块较小的内存空间,它可以看作是当前线程所执行的字节码的行号指示器。
1.2 java虚拟机栈。与程序计数器一样,Java虚拟机栈也是线程私有的,它的
生命周期与线程相同。虚拟机栈描述的是Java方法执行的内存模型:每个方法在执行的同时都会创建一个栈帧,用于存储局部变量表、操作数栈、动态链接、方法出口
等信息。每一个方法从调用直至执行完成的过程,就对应着一个栈帧在虚拟机栈中入栈到出栈的过程。
局部变量表存放了编译期可知的各种基本数据类型,如boolean,byte,char等,以及对象引用类型。
1.3. 本地方法栈,与虚拟机栈作用类似,只不过使用到的方法都是native方法。、
1.4 java堆。此区域唯一目的就是存放对象实例,几乎所有的对象实例都要在这里分配内存。是java垃圾收集的主要区域
1.5方法区与java堆一样,是线程共享的内存区域,用于存储已被虚拟机加载的类信息,常量,静态变量。运行时常量池是方法区的一部分。

二. 垃圾收集

  1. 判断对象是否可被回收
    1.1引用计数算法,给对象添加一个引用计数器,每当有一个地方引用它时,计数器值就加1,引用失效时,计数器值就减一。计数器为零时,代表该对象可被回收。
    缺陷:无法解决互相引用的问题
    1.2可达性分析算法

通过一系列成为GC roots的对象作为起始点,一路向下搜索,当从root到这个对象不可达时,证明这个对象时不可用的,可回收的。
可作为GC Roots对象包括下面几种:
虚拟机栈(栈帧中的本地变量表)引用的对象,方法区中类静态属性,常量引用的对象。本地方法栈中引用的对象。
1.3
强引用:只要强引用还存在,类似于new Object()垃圾收集器永远不会回收掉被引用的对象
软引用,在系统将要发生内存溢出异常之前,将会把这些对象列进回收范围之中进行第二次回收。
弱引用,被弱引用关联的对象只能生存到下一次垃圾收集发生之前。
虚引用:一个对象是否有虚引用的存在,完全不会对其生存时间构成影响,也无法通过虚引用来取得一个对象实例。为一个对象设置虚引用关联的唯一目的就是能在这个对象被收集器回收时收到一个系统通知。
2生存还是死亡:
即使在可达性分析算法中不可达的对象,也并非是非死不可的。要真正宣告一个对象死亡,至少要经历两次标记过程:在判断不可达之后进行第一次标记和一次筛选,筛选的条件是此对象是否有必要执行finalize方法。当对象没有覆盖finalize方法或者finalize方法已经被虚拟机调用过,虚拟机将这两种情况都看作没有必要执行。
如果这个对象呗判定有必要执行finalize方法,那么这个对象将会被放置在一个叫做F-Queue的队列中,稍后GC将会对队列中进行二次标记,如果对象此时将自己赋值给某个类变量或对象的成员变量,重新与引用链关联。第二次标记是它将被移除即将回收的集合。如果没有重新关联引用链,那么它真的被回收了。
3方法区的回收,永久代的垃圾收集主要回收两部分内容,废弃常量和无用的类。废弃常量的判断与可达性方法类似。一个类满足下面三个条件才能算作无用的类:该类所有实例都被回收,加载该类的classloader已经被回收,该类对于的Class对象没有在任何地方被引用,无法再任何地方通过反射访问该方法。虚拟机可以对满足上述3个条件的无用类进行回收,这里说的仅仅是“可以”,而并不是和对象一样,不使用了就必然会回收。
四 回收算法
4.1 标记-清除算法
先标记所有可回收的对象,然后统一回收所有被标记的对象。如下图

主要存在两个问题:效率,标记和清除两个过程效率都不高。另一个是空间问题,标记清除之后会产生大量的不连续内存碎片。
4.2复制算法。
将可用内存分为大小相等的两块,每次只使用其中的一块,当这一块内存用完了,就将还存活着的对象复制到另外一块上面,然后再把已使用过的内存空间一次清理掉。
缺陷:将内存缩小为原来的一半。

现在商业虚拟机都采用这种方式回收新生代。由于新生代98%的对象都是朝生夕死的,所以并不需要按照1:1的比例划分内存空间。而是将内存划分为一块较大的Eden空间和两块较小的Survivor空间。每次使用Eden和其中一块Survivor。回收时将所有存活的对象复制到另外一块Survivor空间上,清理掉Eden和刚才的Survivor空间。默认Eden和Survivor大小是8:1。当Survivor空间不够用时,需要放置老年代空间中。
4.3标记整理算法。
回收后将存活的对象都向内存的一端进行移动,然后直接清理掉边界以外的内存。

4.4枚举根节点。
可达性分析时,会发生GC停顿,因为不可以发生在分析过程中对象引用关系还在不断变化的情况,这点时导致GC进行时必须停顿所有Java执行线程,称为Stop-the-World。
4.4.1安全点
程序执行时并非在所有地方都能停下来开始GC,只有到达安全点时才能暂停。为了让所有线程在GC发生时都能跑到最近的安全点上再停顿下来。有两种方案:抢先式中断和主动式中断。抢先式中断:在GC发生时,首先暂停所有线程,如果发现有线程不在安全点上,就恢复该线程,让它跑到安全点上。现在几乎没有虚拟机采用抢先式中断。
主动式中断:当GC需要中断线程式,不直接对线程操作仅仅简单设置一个标志,各个线程执行时主动去轮询这个标志,发现中断标志为真时,就自己中断挂起。
4.4.2安全区域
安全点机制保证了程序执行时,在不太长的时间内就会遇到可进入GC的安全点,但是,如果一个线程处于sleep或者block状态,这时候线程无法响应jvm的中断请求。这时需要安全区域来解决。我们可以把安全区域看成是被扩展了的安全点。
4.5垃圾收集器
4.5.1Serial收集器。
这个收集器是一个单线程的收集器,只会使用一个Cpu或一条收集线程去完成垃圾收集工作,在它进行垃圾收集时,必须暂停其他所有的工作线程直到收集结束。由于没有线程交互的开销,可以获得最高的单线程收集效率。Serial收集器对于运行在Client模式下的虚拟机来说是一个很好的选择。
4.5.2ParNew收集器
ParNew收集器其实就是Serial的多线程版本。虽然与Serial收集器相比并没有太多创新之处,但它却是许多运行在server模式下的虚拟机中首选的新生代收集器。除了Serial收集器外,目前只有它能与CMS收集器配合工作。ParNew收集器在单CPU的环境中绝对不会有比Serial更好的效果,由于存在线程交互的开销,两个Cpu环境中都不能保证它比Serial效率高。
4.5.3Parallel Scavenge收集器
新生代收集器,采用复制算法。Parallel Scavenge收集器的目标是达到一个可控制的吞吐量。吞吐量=运行用户代码时间/(运行用户代码时间+垃圾收集时间),虚拟机总共运行了100分钟,其中垃圾收集花掉1分钟,那吞吐量就是99%。高吞吐量可以高效率的利用CPu时间。ParallelScavenge收集器提供了两个参数用于精确控制吞吐量,分别是控制最大垃圾收集停顿时间的-XX:Max GCPauseMilli参数以及直接设置吞吐量大小的-XX:GCTimeRatio参数。把MaxGCPauseMills调小并不能使垃圾收集速度变快。将他调小,每次收集的垃圾也变少,使得垃圾收集更加频繁。
4.5.4Serial Old收集器
SerialOld是Serial的老年代版本,采用标记整理算法,同样是一个单线程收集器。
4.5.5Parallel Old收集器
Parallel Old是Parallel Scavenge的老年代版本,使用多线程和标记整理算法,在注重吞吐量以及CPU资源敏感的场合,都可以优先考虑Parallel Old加Parallel Scavenge组合。
4.5.6CMS收集器
是一种以获取最短回收停顿时间为目标的收集器。采用标记清除算法。运作过程分为四个步骤,初始标记,并发标记,重新标记,并发清除。其中初始标记和重新标记依然需要stop the world。CMS收集器的内存回收过程是与用户线程一起并发执行的。优点:并发收集,低停顿。
缺点:
CMS对CPU资源敏感。由于占用了一部分线程,导致用户线程变慢,总吞吐量会变低。
无法处理浮动垃圾—就是在垃圾回收过程中产生的垃圾
标记清除算法会产生内存碎片。为了解决这个问题,CMS收集器在顶不住将要进行FullGC的时候开启内存碎片的合并整理过程,这个过程是没法并发的,停顿时间也变长,
4.5.7G1收集器
HotSpot开发团队赋予它的使命是未来可以替换掉CMS收集器。
特点:
G1能充分利用多CPU,多核环境下的硬件优势,使用多个CPU来缩短stop-theworld时间。
分代收集,虽然G1可以不需要其他收集器配合就能独立管理整个GC堆。
空间整合:G1从整体看基于标记整理算法,从局部看基于复制算法,所以不会产生内存碎片。
收集范围:不再是整个新生代或老年代。将整个java堆划分为多个大小相等的独立区域–region,新生代与老年代不再是物理隔离的了,他们都是region集合。G1根据各个region里面垃圾堆积的价值大小(回收所获得的空间大小以及回收所需时间的经验值),在后台维护一个优先列表,每次根据允许的收集时间,优先回收价值最大的region。
4.6内存分配与回收策略
对象优先新生代的Eden区上。当Eden没有足够空间进行分配时,虚拟机将发起一次Minor GC。新生代总可用空间为Eden区+一个Survivor区的总容量。
新生代GC:Minor GC:频率频繁,回收速度较快
老年代GC:Full GC:出现Full GC经常伴随至少一次的Minor GC。速度比Minor GC慢十倍以上。
4.6.1
大对象:需要大量连续内存空间的java对象,如很长的字符串以及数组。
大对象直接进入老年代。每熬过一次Minor GC,新生代增长一岁,增加到一定程度,默认为十五岁,就会晋升到老年代。如果Survivor空间中相同年龄所有对象大小的总和大于Survivor空间的一半,年龄大于等于该年龄的对象直接进入老年代。
每次MinorGC之前,虚拟机首先检查老年代最大可用连续空间是否大于新生代所有对象总空间,如果这个条件成立,那么MinorGC可以保证安全的,如果不成立,虚拟机检查参数是否允许担保失败,如果允许,则继续检查老年代最大可用连续空间是否大于历次晋升到老年代对象的平均大小,如果大于,尝试进行MinorGC,尽管这次MinorGC是有风险的。如果小于,或者参数设置不允许担保失败,那么这时将进行FullGC。大部分情况是设置允许担保失败,放置fullGC太频繁。
五虚拟机监控工具:
所有工具都在jdk目录下的bin目录里,与java.exe和javac.exe同级。主要用到的可视化工具:jconsole.exe和jvisualvm.exe

如jconsole,双击一个进程就可以对它进行监控。
六类文件结构
魔数:每个class文件头四个字节成为魔数,唯一作用是确定这个文件是否为一个能被虚拟机几首的class文件。由于文件扩展名可以随意改动。Class文件的魔数是0xCAFEBABE咖啡宝贝
七类加载时机
类从被加载到虚拟机内存中开始,到卸载出内存为止,它的整个生命周期包括:加载,验证,卸载,准备,解析,初始化,使用,卸载

猜你喜欢

转载自blog.csdn.net/sinat_36748650/article/details/88665241