深入理解虚拟机之java内存区域:
Java内存区域
- 程序计数器
- java虚拟机栈
- 本地方法栈
- java堆
- 元数据区
- 直接内存
对象的访问方式
1.通过句柄访问
在java堆中分出一块内存进行存储句柄池,这样的话,在栈中存储的是句柄的地址
优点:当对象移动的时候,这样只需要改变句柄的指针,但是栈中的指针不需要改变,因为栈中存储的是句柄的地址
缺点:需要惊醒两次定位,寻找两次指针,开销相对大一点
2.直接访问指针方式
java栈直接与对象进行访问,在java堆中对象的布局就必须考虑如何放置访问类型数据的相关信息,而reference中存储的就是对象地址
优点:速度快,不需要和句柄一样指针定位的开销。
深入理解虚拟机之垃圾回收
如何判断对象是否死亡
1.引用计数法
算法思想:给对象中添加一个引用计数器,每当有一个地方引用它时,计数器值就加1;每当引用失效时,计数器值就减1;任何时刻计数器为0的对象就是不可能再被使用的,也就是所谓的“死去”的对象。
缺点:难以解决对象之间相互循环引用的问题
public class TestReferenceCountingGC {
public Object obj = null;
public void testGc(){
TestReferenceCountingGC obj1 = new TestReferenceCountingGC();
TestReferenceCountingGC obj2 = new TestReferenceCountingGC();
obj1.obj = obj2;
obj2.obj = obj1;
obj1 = null;
obj2 = null;
System.gc();
}
}
在这种相互引用的情况下,两者的计数器值都不为0,所以无法回收他们。
2.可达性分析法
算法思想:通过一系列被称为“GC Roots”的对象作为起始点,从这些节点开始向下搜索,搜索所走过的路径成为引用链,当一个对象当GC Roots没有任何引用链相连时,则证明此对象是不可用的。
可作为GC Roots的对象如下:
- 虚拟机栈中引用的对象
- 方法区中类静态属性引用的对象
- 方法区中常量引用的对象
- 本地方法栈中JNI引用的对象
强引用、软引用、弱引用、虚引用
强引用
程序代码中普遍存在的,比如Object obj = new Object(),只要存在强引用,GC收集器永远不会回收被引用的对象。
软引用
用来描述还有用但非必需的对象,看当前内存是否紧张,如果紧张就会进行回收,否则;不会回收。当程序抛出内存溢出异常时,肯定不存在这种类型的对象。
弱引用
被弱引用关联的对象只能生存到下一次GC发生之前。即每次必被回收。
虚引用
幽灵引用或者幻影引用,一个对象是否有虚引用的存在不会影响到其生存时间,无法通过虚引用获取对象实例。为一个对象设置虚引用关联的唯一目的是希望能在这个对象被回收时受到一个系统通知。
垃圾收集算法及特点
标记-清除算法
分为标记清除两个阶段:先标记出所有需要回收的对象,在标记完成之后统一回收所有被标记的对象。
缺点:效率不高;产生大量空间碎片。
复制算法:将内存按容量大下分为大小相等的两个区域,每次只是用其中的一块。当这一块用完时,就将还存活的对象复制到另一块区域中,然后再把已使用过的内存空间一次清理掉。这样使得每次都是堆这个半区进行内存回收,内存分配时也就不用考虑内存碎片等复杂情况,只要移动栈顶的指针,按顺序分配内存即可。
优点:实现简单,运行高效
缺点:这种算法的代价是将内存缩小为原来的一半。
标记-整理算法
对于“标记-整理”算法,标记过程仍与“标记-清除”算法一样,但是后续步骤不是直接对可回收对象进行清理,而是让所有的存活对象都向一端移动,然后直接清理掉端边界以外的内存。
分代收集算法
当前的商业虚拟机的垃圾收集都是采用“分代收集”(Generational Collection)算法,这种算法并没有什么新的思想,只是根据对象存活周期的不同将内存划分为几块。一般是把堆划分为新生代和老年代,这样就可以根据各个年代的特点采用最适合的收集算法。在新生代中,每次垃圾收集时都发现有大批对象死去,只有少量存活,那就采用复制算法,只需要付出少量存活对象的复制成本就可以完成收集。而老年代中因为对象存活率高、没有额外空间对它进行分配担保,就必须使用“标记-清理”或者“标记-整理”算法来进行回收。
HotSpot为什么要分新生代和老年代
当前虚拟机的垃圾收集都采用分代收集算法,这种算法没有什么新的思想,只是根据对象存活周期的不同将内存分为几块。一般将java堆分为新生代和老年代,这样我们就可以根据各个年代的特点选择合适的垃圾收集算法。
常见的垃圾回收器
Serical收集器
Serical(串行)收集器使最基本、历史最悠久的收集器;这个收集器是一个单线程收集器,它的“单线程”的意义不仅仅意味着它只会使用一条垃圾收集线程去完成垃圾收集工作,更重要的是它在进行垃圾收集工作的时候必须暂停其他所有的工作线程(Stop The World),直到它收集结束。
优点:简单,对单CPU的情况,因为没有多线程交互开销,反而可以更高效。
缺点:Stop The Word
ParNew收集器
ParNew是Serical的多线程版本,使用多条线程进行垃圾回收。其他的可用所有控制参数、收集算法、Stop The World、对象分配原则、回收策略等都与Serical收集器完全一样。
Parallel Scavenge收集器
高效率地利用CPU时间,新生代收集器,使用复制算法,并行多线程收集器;该收集器的目标是达到一个可控制的吞吐量;吞吐量就是CPU用于运行用户代码的时间与CPU总消耗时间的比值。主要适合早后台运算而不需要太多交互的任务。
Paralllel Scavenge 收集器允许采用GC自适应的调节策略,也就是让虚拟机更具收集到的运行时数据自行决定各个分代的大小等与垃圾回收有关的配置。
Serical Old收集器
用于老年代的Serical收集器,单线程,使用“标记-整理“算法
Parrllel Old收集器
Parallel Scavenge的老年代版本,多线程,使用”标记-整理“算法
介绍CMS,G1收集器
CMS收集器
CMS收集器是一种以获取最短回收停顿时间为目标的收集器,重视响应速度。采取标记-清除算法。
4个阶段
初始阶段:值标记GC Roots能直接关联到的对象
并发标记:进行GC RootTracing的过程
重新标记:修正并发标记期间因用户程序继续运作而导致标记发生改变的那一部分对象的标记。
并发清除:其中标记和重新标记两个阶段仍然需要Stop The World,整个过程中耗时最长的并发标记和并发清除过程中收集器都可以和用户线程一起工作。
CMS收集器对CPU资源非常敏感
CMS收集器无法处理浮动垃圾
由于CMS采用的是清除算法,所以会产生大量空间碎片。
G1收集器
G1:是一款面向服务端的收集器,用于替换CMS收集器。
G1将整个Java对分为两个大小相等的独立区域;新生代和老年代不再是物理隔离的,都由一组不连续的Region组成。
G1收集器的特点:
- 并发与并行:充分利用多CPU缩短Stop The World的停顿时间,在收集过程中用并发的方式让java线程继续执行。
- 分代收集:仍然有分代的概念,不需要其他收集器的配合,独立管理整个GC堆。
- 空间整合:从整体来看,是基于”标记-整理“算法实现的,从局部看是基于”复制“算法的,在运行期间不会产生内存碎片。
- 可预测的停顿:G1跟踪各个Region里垃圾堆积值的价值大小,维护一个优先队列,每次根据允许的时间,优先返回价值最大的区域。
MInGC和FullGC区别
MinGC 次收集:在新生代发生的垃圾收集,速度块,发生频繁
FullGC 主收集:发生在老年代的垃圾收集,一般伴随一次MinorGC
虚拟机性能监控和故障处理工具
JVM调优的常见命令行工具有哪些?
JDK命令行工具
jps:虚拟机进程状况工具
jstat:虚拟机统计信息监视工具
jinfo:Java配置信息工具
jmap:java内存映射工具
jhat:虚拟机堆转储快照分析工具
jstack:Java堆栈跟踪工具
HSDIS:JIT生成代码反汇编
JDK的可视化工具
JConsole:java监视与管理控制台
VisualIVM:多合一故障处理工具
深入理解虚拟机之类的文件结构
简单介绍Class类文件结构
Class文件是一组以8位字节为基础的单位的二进制流,各个数据项目严格按照顺序紧凑地排列在class文件中,中间没有添加任何分隔符。
- 魔数:判断是否为一个能被虚拟机接受的Class文件
- 常量池:按顺序存储
- 类索引:确定这个类的全限定名
- 父类索引:确定这个类的父类的全限定名
- 接口索引:用来描述这个类实现了那些接口
- 字段表集合:用于描述接口或者类中声明的变量
- 方法表集合,属性表等