深入理解Java虚拟机-总结

1、JAVA内存区域

线程私有的包括:

程序计数器

l  若正在执行的是java方法,则计数器记录的是正在执行的字节码指令的地址

l  若正在执行的是native方法,则计数器为空

l  该区域是唯一一个不会导致OutOfMemoryError的区域

虚拟机栈

l  描述的是Java方法执行的内存模型:每个方法都会创建一个栈帧用于存储局部变量表,操作数栈,动态链接,方法出口等信息

l  局部变量表存放了编译期可知的基本数据类型,对象引用,和returnAddress类型(指向一条字节码指令地址),局部变量表的内存空间在编译器确定,在运行期不变

l  可导致两种异常:线程请求的栈深度大于虚拟机允许的深度-StackOverflowError;虚拟机无法申请到足够的内存-OutOfMemoryError        

本地方法栈

l  和虚拟机栈类似,但它是为Native方法服务的

线程共享的包括:

l  java堆是被所有线程共享的内存区域,在虚拟机启动时创建,用来分配对象实例和数组

l  堆是垃圾回收器主要管理的区域,可分为新生代和老年代,新生代分为有Eden 空间、From Survivor空间、To Survivor空间

l  大小可通过 -Xmx 和 -Xms 控制

方法区

l  用来存放虚拟机加载的类信息,常量,静态变量,即时编译器编译后的代码等信息

l  GC会回收该区域的常量池和进行类型的卸载

运行时常量池

l  Class文件的常量池用于存放编译期生成的各种字面量和符号引用,这部分内容将在类加载后存放在运行时常量池中

l  还把翻译出来的直接引用也放在运行时常量池中,运行时产生的常量也放在里面

直接内存

l  并不是虚拟机运行时数据区的一部分,也不是Java虚拟机规范中定义的内存区域,是NIO使用Native函数库直接分配堆外内存

2、HotSpot虚拟机在Java堆中对象分配、 布局和访问的全过程

对象创建

l  虚拟机遇到一条new指令时, 首先将去检查这个指令的参数是否 能在常量池中定位到一个类的符号引用,并且检查这个符号引用代表的类是否已被加载、解析和初始化过。如果没有,那必须先执行相应的类加载过程

l  内存分配

指针碰撞

空闲列表

安全性

CAS配上失败重试

本地线程分配缓冲(TLAB)

l  内存空间初始化

l  对对象进行必要的设置(例如这个对象是哪个类的实例、如何才能找到 类的元数据信息、对象的哈希码、对象的GC分代年龄等信息)

l  执行init,完成

对象的内存布局

l  对象头

第一部分用于存储对象自身的运行时数据,如哈希码(HashCode)、GC分代年龄、锁状态标志、线程持有的锁、偏向 线程ID、偏向时间戳等

另外一部分是类型指针,即对象指向它的类元数据的指针, 虚拟机通过这个指针来确定这个对象是哪个类的实例实例数据

l  实例数据

是对象真正存储的有效信息,也是在程序代码中所定义的各种 类型的字段内容。 无论是从父类继承下来的,还是在子类中 定义的,都需要记录起来对齐填充

l  对齐补充

对象的访问定位

使用句柄

直接指针(Sun HotSpot)

3、垃圾收集和内存分配

引用计数法

l  思想:给对象设置引用计数器,每引用该对象一次,计数器就+1,引用失效时,计数器就-1,当任意时候引用计数器的值都为0时,则该对象可被回收

l  Java不适用原因:无法解决对象互相循环引用的问题

可达性分析法

以GC Roots为起点,从这些起点开始向下搜索,经过的路径称为引用链。若一个对象到GC Roots之间没有任何引用链,则该对象是不可达的。

可作为GC Roots的对象有

l  虚拟机栈(栈帧中的局部变量表)中引用的对象

l  方法区中类静态属性引用的对象

l  方法区中常量引用的对象

l  本地方法栈中JNI(Native方法)引用的对象

在可达性分析过程中,对象引用类型会对对象的生命周期产生影响

JAVA中有这几种类型的引用:

l  强引用:只要该引用还有效,GC就不会回收

l  软引用:内存空间足够时不进行回收,在内存溢出发生前进行回收、用SoftReference类实现

l  弱引用:弱引用关联的对象只能存活到下一次Gc收集、用WeakReference类实现

l  虚引用:无法通过虚引用获得对象实例,也不会对对象的生存时间产生影响、唯一目的:当该对象被Gc收集时,收到一个系统通知。用PhantomReference类实现

一个对象真正不可用,要经历两次标记过程:

l  首先进行可达性分析,筛选出与GC Roots没用引用链的对象,进行第一次标记和筛选,筛选条件是是否有必要执行finalize()方法。若对象有没有重写finalize()方法,或者finalize()是否已被jvm调用过,则没必要执行,GC会回收该对象,若有必要执行,则该对象会被放入F-Queue中,由jvm开启一个低优先级的线程去执行它(但不一定等待finalize执行完毕)

l  第一次标记后,GC将对F-Queue中的对象进行第二次标记,Finalize()是对象最后一次自救的机会,若对象在finalize()中重新加入到引用链中,则它会被移出要回收的对象的集合,其他对象则会被第二次标记,进行回收

JAVA中的垃圾回收算法有:

标记-清除(Mark-Sweep)

两个阶段:标记, 清除

缺点:两个阶段的效率都不高;容易产生大量的内存碎片

复制(Copying)

把内存分成大小相同的两块,当一块的内存用完了,就把可用对象复制到另一块上,将使用过的一块一次性清理掉

缺点:浪费了一半内存

标记-整理(Mark-Compact)

标记后,让所有存活的对象移到一端,然后直接清理掉端边界以外的内存

分代收集

把堆分为新生代和老年代

新生代使用复制算法

将新生代内存分为一块大的Eden区和两块小的Survivor;每次使用Eden和一个Survivor,回收时将Eden和Survivor存活的对象复制到另一个Survivor(HotSpot的比例Eden:Survivor = 8:1)

老年代使用标记-清理或者标记-整理

HotSpot的算法实现

       枚举根节点

              OopMap数据结构记录哪些位置是引用

       安全点

              哪些位置可以生产OopMap

              以是否具有让程序长时间执行的特征为标准进行选定

              抢先式中断(不采用)和主动式中断

       安全区域

              一段代码片段中,引用不会发生变化

垃圾收集器:

Serial(串行收集器)

特性:单线程,stop the world,采用复制算法,简单高效

应用场景:在Client模式下默认的新生代收集器

ParNew

特点:是Serial的多线程版本,采用复制算法

应用场景:在Server模式下常用的新生代收集器,可与CMS配合工作

Parallel Scavenge

特点:并行的多线程收集器,采用复制算法,吞吐量优先,有自适应调节策略

应用场景:需要吞吐量大的时候

SerialOld

特点:Serial的老年代版本,单线程,使用标记-整理算法

Parallel Old

Parallel Scavenge的老年代版本,多线程,标记-整理算法

CMS

       处理过程:

l  初始标记:stop the world 标记GC Roots能直接关联到的对象

l  并发标记:进行GC Roots Tracing

l  重新标记:stop the world;修正并发标记期间因用户程序继续运作而导致标记产生变动的那一部分对象的标记记录

l  并发清除:清除对象

特点:以最短回收停顿时间为目标,使用标记-清除算法

优点:并发收集,低停顿

缺点:

l  对CPU资源敏感

l  无法处理浮动垃圾(并发清除时,用户线程仍在运行,此时产生的垃圾为浮动垃圾)

l  产生大量的空间碎片

G1

面向服务端应用,将整个堆划分为大小相同的region。

       特点

l  并行与并发

l  分代收集

l  空间整合:从整体看是基于“标记-整理”的,从局部(两个region之间)看是基于“复制”的。

l  可预测的停顿:使用者可明确指定在一个长度为M毫秒的时间片段内,消耗在垃圾收集上的时间不得超过N毫秒。

       处理过程:

l  初始标记:stop the world 标记GC Roots能直接关联到的对象

l  并发标记:可达性分析

l  最终标记:修正在并发标记期间因用户程序继续运作而导致标记产生变动的一部分标记记录

l  筛选回收:筛选回收阶段首先对各个Region的回收价值和成本进行排序,根据用户所期望的GC停顿时间来制定回收计划

内存分配规则

触发GC又涉及到了内存分配规则:(对象主要分配在Eden,若启动了本地线程分配缓冲,将优先在TLAB上分配)

l  对象优先在Eden分配

当Eden区没有足够的空间时就会发起一次Minor GC

l  大对象直接进入老年代

典型的大对象是很长的字符串和数组

l  长期存活的对象进入老年代

每个对象有年龄计数器,每经过一次GC,计数器值加一,当到达一定程度时(默认15),就会进入老年代,年龄的阈值可通过参数 -XX:MaxTenuringThreshold设置

对象年龄的判定

Survivor空间中相同年龄所有对象大小的总和大于Survivor空间的一半,年龄大于等于该年龄的对象就可直接进入老年代,无须等到MaxTenuringThreshold要求的年龄

l  空间分配担保

发生Minor GC前,jvm会检查老年代最大可用的连续空间是否大于新生代所有对象总空间,若大于,则Minor GC是安全的,若不大于,jvm会查看HandlePromotionFailure是否允许担保失败,若不允许,则改为一次Full GC,若允许担保失败,则检查老年代最大可用的连续空间是否大于历次晋升到老年代对象的平均大小,若大于,则尝试进行Minor GC;若小于,则要改为Full GC

最后提一下也会回收方法区:

永久代中主要回收两部分内容:废弃常量和无用的类

废弃常量回收和对象的回收类似

无用的类需满足3个条件

l  该类的所有实例对象已被回收

l  加载该类的ClassLoader已被回收

l  该类的Class对象没有在任何地方被引用,无法在任何地方通过反射访问该类的方法

4、JVM常用命令

JDK命令行工具

Jps

         查看虚拟机进程

Jstat

         监视虚拟机各种运行状态信息

Jinfo

         查看和调整虚拟机各项参数

Jmap

         生产堆转储快照(一般称为heapdump或dump文件)

查询finalize执行队列、Java堆和永久代的详细信息,如空间使用率、当前用的是哪种收集器

Jhat

         分析堆转储快照

Jstack

         生产虚拟机当前时刻的线程快照(一般称为threaddump或者javacore文件)

可视化工具

Jconsole

VisualVM

         目前为止JDK发布的功能最强大的运行监视和故障处理工具

         对应用程序的实际性能影响很小,可以直接应用在生产环境中

         插件形式扩展

未完待续...

猜你喜欢

转载自my.oschina.net/jzgycq/blog/1802632