深入理解java虚拟机笔记(上)

JVM

由于深入java 虚拟机 基于jdk 1.7 版本 的Hotspot,所以下面的内容均只该版本的jvm

内存区域

程序计数器

  • 程序计数器是一块较小的内存,线程私有,记录虚拟机字节码执行行数,线程切换后可以恢复到上次执行的位置,如果是native方法调用外部,计数器值为空

java虚拟机栈

  1. stacks 是线程私有的,它的声明周期与线程相同,java方法执行的内存魔属性,每个方法执行,都会创建一个栈帧 stack frame 用于存放局部变量、操作数栈、动态链、方法出口灯,每个方法执行开始创建栈帧并入栈,执行后出栈。
  2. 其中基本数据类型(int、byte、char)会在栈帧中申请空间,放入局部变量空间,64位长度long占用2两局部变量空间。
  3. 线程请求栈深度大于机器所允许深度,会抛出Stack Overflow,无法申请到内存,会抛出outOfMemoryError。

本地方法栈

  • 本地方法栈为虚拟机执行native方法,hotspot 将本地方法区和虚拟机栈合并,本地方法区也会抛出Stack Overflow、outOfMemoryError

java堆

  • 堆是多有线程共享,用于存放对象实例,几乎所有的对象都在这里分配内存,java堆可以分为新生代和老年代,其中新生代细分为,eden、from survivor to survivor 空间等。java 堆可以处于物理上不连续的空间中。逻辑连续即可。

方法区

  1. 各个线程共享的内存区域,存放已被虚拟机加载的类信息、常量、静态变量、及时编译后的字节码,在hotspot 中又称为永久代(Perm space)
  2. perm 是方法区的一种实现。方法区是jvm 一种规范。对 HotSpot 虚拟机来说,可以把永久代直接等同于方法区,其中会存储已经被jvm 加载的类信息,常量,静态变量,即时编译器编译后的代码等数据

运行时常量池

  • 是方法区的一部分

直接内存

  • 通过native库申请堆外内存

对象创建

  1. 执行一条指令,检查指令参数是否在常量池中定位到引用,检查这个引用是否被加载和解析、初始化
  2. 类加载通过后,申请分配内存,内存分配分为两种,指针碰撞、空闲列表,哪种方式由java 堆决定
  3. 还需要考虑并发修改指针问题,CAS+重试方式,CPU支持CUP 原语。所以是安全的。

对象访问

  1. 句柄方式
  2. 指针访问,指针访问速度快,hotspot 使用指针访问
  • 栈中的reference 保存句柄,通过句柄访问对象,好处:对象地址变化,栈中reference信息无需变更
  • 栈中的reference 保存对象指针,直接访问 hotSpot 使用指针访问,优点:速度快

垃圾收集器和内存分配策略

  1. 方法区

回收标记算法

  1. 引用计数法:每一个地方引用他,计数器加1,删除引用,计数器减1。实现简单,相互引用会有问题
  2. 可行性分析算法:通过GC Root 对象作为起点,搜索引用链,假如GC Root 到某个对象不可达,则该对象可以回收。回收对象至少执行两次标记,才会进行回收。
    针对GC Root 无引用对账,放入F-Queue 中执行finalize(),假如已执行后者未重写finalize(),则不执行,等待第二次标记。

引用

  1. 强引用: 只要引用存在GC不会回收该对象
  2. 软引用: 系统内存溢出前,将会把这些对象进行回收,如果这次回收仍不够内存,抛出内存溢出
  3. 弱引用: 非必须对象,下次GC 会清除
  4. 虚引用: 设置虚引用,对象在回收时,返回系统通知

方法区回收

  • 方法区回收,涉及两类:废弃常量(常量池)、无用的类

垃圾收集算法

  1. 标记清楚法(mark-sweep)是基础的手机算法a, 标记、清除两个步骤效率低,并且会造成内存碎片,针对大对象,可能会进行二次回收
  2. 复制算法,将内存分为两块,把回收后的内存,重新这只头指针,复制到另外一个内存块上。消除内存碎片。对Eden 进行回收,将eden 和 from survivor存活对象通过复制转移到 to survivor区,再清理掉Eden和 from survivor区,所以eden和survivor 默认比例为8:1,假如存活的对象超出to survivor,老年代将会分配担保
  3. 标记-整理算法 由于老年代存活率较高,所以使用复制法,需要进行较多的复制操作,效率较低。标记-整理和标记清除前期操作类似,都进行标记,将存活对象向一端移动,然后直接删除边界意外的内存。
  4. 分代收集算法:大部分回收算法,都是将堆分为年轻代和老年代,年轻代使用标记-复制,老年代使用标记-清除,标记-整理,因为老年代回收率不高,没有额外空间。

垃圾收集器

  1. Serial 回收器是最早的新生代收集器,只支持单核,执行垃圾回收时,需要停止其他所有工作线程,直到收集结束。简单高效,适合用于client、单核计算机。
    SerialOld 是单核的老年代收集器
  2. ParNew收集器 是Serial多线程版本,处理新生代,ParOld 是老年代的多线程版本

  3. Parallel scavenge 收集器 是新生代收集器,也是并行的收集器,和ParNew 的区别,Parallel 更注重于回收时间/运行时间的比值。
  4. CMS(Concurrrent Mark Swap)
    收集区域:老年代收集器,用户和GC可以并发进行,使用标记-清楚算法实现,内存物理连续。Minor GC 会清理年轻代的内存、Major GC /FULL GC是清理老年代。
    特点:并发执行对老年代进行GC(FULL),用户可以执行。缺点:占用资源、标记-清除导致内存碎片化,分配大内存可能又导致FULL GC、需要预留空间在GC的时候,假如GC期间,老年代满了,就会触发SerialOld 收集器进行回收
    CMS收集步骤:初始标记、并发标记、重新标记、并发清理
    FullGC原因:Promotion Failure 年轻代晋升老年代没有空间 、Concurrent Mode Failure 并发GC老年代预留空间满了
  5. G1(Garbage First)收集器 是最前沿的收集器
    收集区域:年轻代、老年代,分为yongGc、mixGc 没有FULLGC 概念
    特点:并发运行GC和java程序、对年轻代分代收集、基于标记-整理,不会产生碎片、可预测停顿(可以配置最少gc时间)
    G1收集步骤:初始标记、并发标记、最终标记、筛选标记
    G1内存划分:Eden Survivor Old Generation,将堆划分为逻辑上连续的region。这样可以针对region进行gc 不需要对整个年轻代和老年代gc。

对象分配和回收策略

  1. 基本所有新创建对象分配到eden 区,当申请对象超过剩余yongGen区域时,直接进入老年代
  2. 大对象直接进入老年代,可以通过配置
  3. 长期未回收对象,年龄达到指定标记次数
  4. 动态对象回收,相同年龄到达surivor空间的一半,直接转入老年代
  5. 空间分配担保,在发生minorGC之前,虚拟机会检查新生代对象的数量和老年代空闲的空间,如果大于,会检查HandlePromotionFailure = true 如果是ture 则进行GC,false 进行fullGC,HandlePromotionFailure含义是否surivor中的空间不足,直接进入老年代。极端情况,oldGen 也不够,会进行fullGC

https://blog.csdn.net/coderlius/article/details/79272773