基础背景
运行时数据区域
虚拟机结构图
- 程序计数器:
因为线程会切换,因此每个线程独有一份,用作在执行过程中记录编译后的class文件行号.
- 虚拟机栈:以栈帧为单位存放局部变量.
- Native方法栈:和虚拟机栈类似,不过,一个本地方法是这样一个方法:该方法的实现由非java语言实现,比如C语言实现。很多其它的编程语言都有这一机制,比如在C++中,你可以告知C++编译器去调用一个C语言编写的方法.我们知道,当一个类第一次被使用到时,这个类的字节码会被加载到内存,并且只会回载一次。在这个被加载的字节码的入口维持着一个该类所有方法描述符的list,这些方法描述符包含这样一些信息:方法代码存于何处,它有哪些参数,方法的描述符(public之类)等等。如果一个方法描述符内有native,这个描述符块将有一个指向该方法的实现的指针。这些实现在一些DLL文件内,但是它们会被操作系统加载到java程序的地址空间。当一个带有本地方法的类被加载时,其相关的DLL并未被加载,因此指向方法实现的指针并不会被设置。当本地方法被调用之前,这些DLL才会被加载,这是通过调用java.system.loadLibrary()实现的。
- 方法区:运行时常量池,存放编译器的字面量和符号引用,也可以在运行时动态加入.
- java堆:存放对象的实例,是垃圾回收的主战场,
创建一个对象
- 比如执行 new MyClass();
- 去常量池中寻找,查看类是否被加载.如果没加载,则加载class.
- 在java堆中分配内存空间,方式有以下两种:
- 指针碰撞:把指针向空闲对象移动与对象占用内存大小相等的距离,使用的收集器有Serial、ParNes等
- 空闲列表:虚拟机维护一个列表,记录可用的内存块,分配给对象列表中一块足够大的内存空间,使用的收集器有CMS等.
- 如何分配内存,由垃圾回收器决定.
- 内存的具体分配过程中有同步和预留空白区的方式
- 内存分配好后,再执行init()方法,初始化实例.
对象头
- 对象头主要记录对象的hashcode,GC标记,元数据地址,以及关于对象锁的使用,年龄代,偏向线程等。
- hash用于快速寻找对象
- 对象头大小32bit/64bit,由虚拟机决定
- 实例数据区的数据类型,按照相似放在一起.
对象中的访问定位
方式
- 句柄池
- 句柄池从堆中划分
- 由实例地址和类型数据地址构成
- 指针
- 可直接通过指针访问到实例对象
优劣
句柄的使用,方便了实例位置的改变,可以不改变引用,但是访问速度相对于指针低一些.
JVM垃圾回收
判断可否回收的算法
1.引用计数算法:
给对象中添加一个引用计数器,每当有一个地方引用它时,计数器值就加1;当引用失效时,计数器值就减1;任何时刻计数器都为0的对象就是不再被使用的,垃圾收集器将回收该对象使用的内存。
- 可达性分析算法:
通过一系列的名为GC Root的对象作为起点,从这些节点向下搜索,搜索所走过的路径称为引用链,当一个对象到GC Root没有任何引用链相连时,则该对象不可达,该对象是不可使用的,垃圾收集器将回收其所占的内存。
实例的位置:
- java虚拟机栈(栈帧中的本地变量表)中的引用的对象。
- 方法区中的类静态属性引用的对象。
- 方法区中的常量引用的对象。
- 本地方法栈中JNI本地方法的引用对象。
- 不可达对象到死亡还需要两次标记,第一次,标记后进入F-Queue队列,第二次标记时只有finalize()中有拯救自己的方法的实例才能自救成功,比如将自己应用给其它变量.
垃圾回收算法
方法区的回收
常量池的回收
- 没有被引用,即可被回收
class对象回收
- 所有实例都被回收
- 所有classLoader都被回收
- java.lang.class对象没有被任何地方引用,即无法在任何地方使用反射访问类.
- 最终是否被回收,还得看JVM参数配置
java堆回收算法
- 标记清除算法: 先标记判定,再一次性清除.
- 产生了大量碎片,且效率低下
- 复制算法: 把可用内存划分为两块,一块用完后,就将活下来的实例放到另一块内存区.
- 优缺点:没有了碎片化问题,但内存大小减少了一半
- 标记整理算法: 在标记-清除算法基础上做了改进,标记阶段是相同的标记出所有需要回收的对象,在标记完成之后不是直接对可回收对象进行清理,而是让所有存活的对象都向一端移动,在移动过程中清理掉可回收的对象,这个过程叫做整理。
- 标记-整理算法相比标记-清除算法的优点是内存被整理以后不会产生大量不连续内存碎片问题。复制算法在对象存活率高的情况下就要执行较多的复制操作,效率将会变低,而在对象存活率高的情况下使用标记整理算法效率会大大提高。
-
分代收集算法: 根据内存中对象的存活周期不同,将内存划分为几块,java的虚拟机中一般把内存划分为新生代和年老代,当新创建对象时一般在新生代中分配内存空间,当新生代垃圾收集器回收几次之后仍然存活的对象会被移动到年老代内存中,当大对象在新生代中无法找到足够的连续内存时也直接在年老代中创建。现在的Java虚拟机就联合使用了分代复制、标记-清除和标记-整理算法.
java虚拟机垃圾收集器关注的内存结构如下:
新生代
研究表明,新生代中98%的对象是朝生夕死的短生命周期对象,所以不需要将新生代划分为容量大小相等的两部分内存,而是将新生代分为Eden区,Survivor from和Survivor to三部分,其占新生代内存容量默认比例分别为8:1:1,其中Survivor from和Survivor to总有一个区域是空白,只有Eden和其中一个Survivor总共90%的新生代容量用于为新创建的对象分配内存,只有10%的Survivor内存浪费,当新生代内存空间不足需要进行垃圾回收时,仍然存活的对象被复制到空白的Survivor内存区域中,Eden和非空白的Survivor进行标记-清理回收,两个Survivor区域是轮换的。
年老代
- 年老代中的对象一般都是长生命周期对象,对象的存活率比较高,因此在年老代中使用标记-整理垃圾回收算法。
- Java虚拟机对年老代的垃圾回收称为MajorGC/Full GC,次数相对比较少,每次回收的时间也比较长。
堆分配和回收策略
分配
- 优先在Eden上分配,空间不足,虚拟机发起minor GC.
- 大对象直接进入老年代,防止折磨新生代空间.[参数设置 -XX:PretrnureSizeThreshold=[字节数]]
- 长大后的对象进入老年代,在survivor中熬过一次,就长一岁,15岁时就进入老年代[阈值设置 -XX:MaxTenuringThreshold=[岁数]]
- 相同年龄的对象,若大于或等于空间的一半,也直接进入老年代.
oracle java虚拟机
- 官方使用的HotSpot虚拟机
- 默认老年代收集器MarkSweep,新生代收集器Scavenge
- MarkSweep收集器:是以牺牲吞吐量为代价来获得最短回收停顿时间的垃圾回收器。对于要求服务器响应速度的应用上,这种垃圾回收器非常适合。在启动JVM参数加上-XX:+UseConcMarkSweepGC ,这个参数表示对于老年代的回收采用CMS。CMS采用的基础算法是:标记—清除。
- 流程:
初始标记 :在这个阶段,需要虚拟机停顿正在执行的任务,官方的叫法STW(Stop The Word)。这个过程从垃圾回收的"根对象"开始,只扫描到能够和"根对象"直接关联的对象,并作标记。所以这个过程虽然暂停了整个JVM,但是很快就完成了。
并发标记 :这个阶段紧随初始标记阶段,在初始标记的基础上继续向下追溯标记。并发标记阶段,应用程序的线程和并发标记的线程并发执行,所以用户不会感受到停顿。
并发预清理 :并发预清理阶段仍然是并发的。在这个阶段,虚拟机查找在执行并发标记阶段新进入老年代的对象(可能会有一些对象从新生代晋升到老年代, 或者有一些对象被分配到老年代)。通过重新扫描,减少下一个阶段"重新标记"的工作,因为下一个阶段会Stop The World。
重新标记 :这个阶段会暂停虚拟机,收集器线程扫描在CMS堆中剩余的对象。扫描从"跟对象"开始向下追溯,并处理对象关联。
并发清理 :清理垃圾对象,这个阶段收集器线程和应用程序线程并发执行。
并发重置 :这个阶段,重置CMS收集器的数据结构,等待下一次垃圾回收。
- 新生代收集器,复制算法,并行收集,面向吞吐量要求(吞吐量优先收集器)。
吞吐量=用户代码运行时间/(用户代码运行时间+垃圾回收时间)
-XX:MaxGCPauseMillis:控制最大垃圾收集停顿时间,大于零的毫秒数。
-XX:GCTimeRatio:吞吐量大小,0到100的整数,垃圾收集时间占总时间的比例,计算1/(1+n)gc时间占用比例。
-XX:UseAdaptiveSizePolicy:打开之后,就不需要设置新生代大小(-Xmn),Edian,survivor比例及(-XX:SurvivorRatio)晋升老年代年龄(-XX:PretenureSizeThreshold),虚拟机根据系统运行状况,调整停顿时间,吞吐量, GC自适应调节策略,区别parnew。
附:JVM参数整理
参数调优建议
- 永久代:-XX:PermSize20M -XX:MaxPermSize20M
- 堆大小: -Xms20M -Xmx20M
- 新生代: -Xmn10M
- Eden与survior比率: -XX:SurvivorRation=8
- 让大对象直接进入老年代: -XX:PretrnureSizeThreshold=1B
- 年龄阈值:-XX:MaxTenuringThreshold=15
日志命令:参考连接
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。