JVM知识点整理

java运行时区域

在这里插入图片描述

线程私有区域

  1. 程序计数器
    程序计数器线程私有的,可以看做是当前线程所执行的字节码行号指示器。字节码解释器工作时就是用过这个程序计数器来选取下一条执行的字节码指令。因为一个处理器只能执行一个线程,所以线程切换时就是通过程序计数器来恢复到正确的执行位置。
    如果线程正在执行一个java方法,这个计数器记录的是正在执行的虚拟机字节码指令的地址;
    如果线程执行的是本地方法,则计数器为空。
  2. java虚拟机栈
    一般所说的栈就是指java虚拟机栈,这也是线程私有的,生命周期与线程相同。当线程中的方法执行时,虚拟机会为每个方法创建一个栈帧用于存储局部变量表、操作数栈、动态连接、方法出口等。方法的执行到结束,就是虚拟机栈的入栈到出栈。
    局部变量表:用于存放编译期可知的克重java虚拟机基本数据类型、对象引用以及returnAddress类型。在局部变量表中以槽(Slot)存储,除了64位的long和double类型需要两个槽,其余都只占一个槽
    异常情况:
    StackOverflowError:线程请求的栈深度大于虚拟机允许的最大深度;
    OutOfMemoeyError:当java虚拟机栈容量可以动态扩展时,扩展无法申请到足够内存。
  3. 本地方法栈
    类似于java虚拟机栈,不同于java虚拟机栈为虚拟机执行java方法服务;本地房发展为虚拟机使用本地方法服务。也是线程私有的。

线程共享区域

  1. 方法区
    用于存储被虚拟机加载的类型信息、常量、静态变量、即时编译器编译后的代码缓存等数据。
    异常:OutOfMemoryError异常:方法区无法满足新的内存分配需求。

  2. 堆是在java内存中占用最大的一块,用来存放java实例。同时是垃圾收集器管理的内存区域,所以又被称为“GC堆”

其他说明

  1. 运行时常量池
    运行时常量池时方法区的一部分,用于存放编译期生成的各种字面量与符号引用,这些字面量和符号引用是在类加载后存放到方法区的运行时常量池中的。
    常量池具备动态性,数据可以在编译期产生进入常量池,也可以在运行期间将新的常量放入常量池。
    异常:OutOfMemoryError异常:常量池无法申请到内存。
  2. 直接内存
    直接内存并不是java虚拟机运行时数据区的一部分。所以不受java堆的限制,但受到内存的限制。
    所以存在异常:OutOfMemoryError异常:各个内存区域总和大于物理内存限制(包括物理的和操作系统级的限制)

对象的创建过程

  1. java虚拟机遇到字节码new指令;
  2. 检查这个指令的参数能够在常量池中定位到一个类的符号引用,检查这个符号引用是否已被加载、解析和初始化过
  3. 如果没有,执行类加载过程;
  4. 如果通过类加载检查,虚拟机为新生对象分配内存。

内存分配方法:
【 指针碰撞 】:假设java堆中的内存是规整的,所有使用过的内存放在一边,空闲的内存放在另一边,中间放着一个指针作为分界点的指示器。分配内存就是将指针向空闲的方向挪动一段与对象大小相等的距离。
【 空闲列表 】:java堆的内存不是规整的,被使用的内存和空闲内存相互交错。这时虚拟机需要维护一个列表,列表记录哪些内存块可用。在分配对象时吗,找到一块足够大的空间划分给对象实例,并更新列表记录。

内存分配时,并不能保证线程安全:
第一种方法:java虚拟机采用 CAS+失败重试 的方式保证更新操作的原子性;
第二种方法:给每个线程预先分配一小块内存,称为本地线程分配缓冲(TLAB),哪个线程要分配内存,就在哪个线程的本地缓冲区中分配,只有本地缓冲区用完了,分配新的缓存区时才需要同步锁定。

  1. 内存分配完成后,虚拟机将分配到的内存空间初始化为零值。(如果采用TLAB保证线程安全的,可以提前至TLAB分配时顺便进行),这个操作保证了对象的实例字段在不赋初始值就可以直接使用。
  2. 虚拟机对对象的对象头进行一些必要的设置:对象时哪个类的实例,如何找到类的元数据信息,对象的哈希码,对象的GC分代年龄等
  3. new指令之后执行init()方法。

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

因为程序计数器、虚拟机栈以及本地方法栈的生命周期同线程一起,所以这几个区域的内存分配和回收都具备确定性吗,不需要考虑回收问题。

判定对象存活的方法

  1. 引用计数器(Reference Counting)
    在对象总添加一个引用计数器,每当有地方引用它,计数器就+1;当引用失效,计数器就减1;
  2. 可达性分析算法(Reachablility Analysis)
    如果某个对象到GC Roots间没有任何引用链相连,或者说从GC Roots到这个对象不可达,证明对象不能再被使用。

GC Roots可以包括以下几类:
1)在虚拟机栈(栈帧中的本地变量表)中引用的对象
2)在方法区中静态属性引用的对象。
3)方法区中常量引用的对象
4)本地方法栈中JNI引用的对象
5)java虚拟机内部的引用
6)所有被同步锁持有的对象
7)反映Java虚拟机内部情况的JMXBean、JVMTI中注册的回调、本地代码缓存等。
8) 根据用户所选用的垃圾收集器以及当前回收的内存区域不同,还可以有其他对象“临时性”地加入,共同构成完整GC Roots集合。

关于引用

概念:如果reference类型的数据中存储的数值代表的是另外一块内存的起始地址,就称该reference数据是代表某块内存、某个对象的引用。
引用分类:

  1. 强引用(Strongly Reference)
    是指程序中普遍存在的引用赋值,只要强引用关系还在,垃圾收集器就不会回收被引用对象。
  2. 软引用(Soft Reference)
    描述一些还有用、但非必须的对象。在内存将要溢出时,这些对象会在下一次内存回收时回收。
  3. 弱引用(Weak Reference)
    描述非必须对象,只会生存到会在下一次垃圾收集发生。
  4. 虚引用(Phantom Reference)
    一个对象是否有虚引用的存在,完全不会对其生存时间构成影响,也无法通过虚引用来取得一个对象实例。为一个对象设置虚引用关联的唯一目的只是为了能在这个对象被收集器回收时收到一个系统通知。

判断对象死亡的过程

在这里插入图片描述

方法区的回收

  1. 常量的回收:也是用过判断有没有别的对象引用这个常量吗,如果没有,系统将会把这个常量清理出常量池。
  2. 类的回收
    类的回收要同时满足下面三个条件:
    2.1 该类所有的实例都已经被回收;
    2.2 加载该类的类加载器已经被回收;、
    2.3 该类对应的java.lang.Class对象没有任何地方被引用,即任何地方都无法反射该类的方法。

垃圾回收算法

相关收集名词:
【新生代收集 (Minor GC/ Young GC)】:指目标是新生代的垃圾收集;
【老年代收集(Major GC/Old GC)】:指目标是老年代的垃圾收集
【混合收集(Mixed GC)】:指目标是收集整个新生代以及部分老年代的垃圾收集。(只有G1收集器会有这个行为)
【整堆收集(Full GC)】:收集整个java堆和方法区的垃圾收集。

  1. 标记-清除算法(Mark-Sweep)
    将需要回收的对象标记出来(也可以标记存活的对象),然后同一将需要回收的对象清理。
    在这里插入图片描述
    优点:简单直接速度快
    不足:执行效率不稳定;内存空间碎片化。
  2. 标记-复制算法
    将内存分为大小相等的两块,每次只使用其中一块。当这一块的内存用完了,就将还存活的对象复制到另一块内存中,并将这块内存清空。
    改良版标记复制算法:
    将内存划分为较大的Eden空间和两块较小的Survivor空间,默认空间比例是8:1:1,每次内存分配只是用Eden和其中一块Survivor。当发送内存回收时,将将Eden和Survivor中仍然存活的对象一次性复制到另外一块Survivor空间上,然后直接清理掉Eden和已用过的那块Survivor空间。(如果Surivivor空间放不下存活的对象,需要依赖其他内存区域(一般为老年代)进行分配担保)
    优点:解决了标记清除的碎片问题。
    缺点:浪费一部分空间;存活对象多时会非常耗时;需要担保机制
  3. 标记-整理算法
    标记整理的标记过程与标记清除算法一样,但在进行对象回收清理时,会让所有存活的对象统一像内存空间的异端移动,然后直接清理掉边界意外的内存。
    优点:解决了标记清除的空间碎片问题,也不会浪费内存空间;
    不足:性能低下。
  4. 一般在新生代使用的是标记-复制算法,因为标记-复制算法收集速度快,可以避免空间碎片。且新生代只有少量对象存活,所以适用于标记-复制算法;而老年代一般可采用标记清除 or 标记整理算法,看老年代是否需要避免空间碎片而决定使用哪个算法。

垃圾收集器

在这里插入图片描述

  1. Serial收集器
    这是一个单线程工作的收集器,在进行垃圾收集时,其他线程工作必须暂停,直到Serial收集结束。
    但Serial是所有收集器中额外内存消耗最小的,没有线程交互开销。
    在这里插入图片描述

  2. ParNew收集器
    在Serial的基础上,支持多线程并行收集。
    在这里插入图片描述

  3. Parallel Scavenge收集器
    与ParNew非常相似,但他的关注点与其他收集器不同,Parallel Scavenge收集器的目的是达到一个可控制的吞吐量。
    ---------------------------------------老年代------------

  4. Serial Old收集器
    同样是一个单线程收集器,使用标记整理算法。

  5. Parallel Old收集器
    支持多线程并发收集,也是基于标记整理算法实现。
    在这里插入图片描述

  6. CMS收集器
    是一种以获取最短回收停顿时间为目标的收集器。基于标记-清除算法实现的。运行过程分为4个步骤:
    1)初始标记:需要暂停其他线程工作,但仅仅只是标记了GC Roots能关联到的对象,速度很快。
    2)并发标记:从GC Roots的直接关联对象开始遍历针对对象图的过程,耗时长,但不需要暂停用户线程,可以并发进行。
    3)重新标记:修正并发标记期间因为用户线程继续工作导致标识产生变动的那一部分对象的标记记录,此时也需要暂停用户线程。
    4)并发清理:清理删除掉已死亡对象,可以用户线程并发进行。
    在这里插入图片描述
    优点:并发收集、低停顿。
    缺点:
    1)对处理器资源非常敏感,与用户线程并发进行,会导致程序变慢,吞吐量降低。
    2)无法收集“浮动垃圾”,即在并发阶段用户线程产生的新的垃圾对象。
    3)需要给用户线程预留空间提供给用户线程使用。
    4)因为是基于标记-清除算法实现的,所以会产生大量空间碎片。
    -------------全堆收集器-----------------------------------

  7. Garbage First收集器(G1)
    G1主要面向服务端应用的垃圾收集器,是服务端模式下的默认垃圾收集器。G1最大的特点是引入分区的思路,弱化了分代的概念。它是将连续的java堆划分成多个大小相等的独立区域(Region),每个Regin都可以根据需要扮演Eden空间、Survivor空间或者是老年代空间。收集器能够根据不同角色的Region采用不同的策略去处理。
    对于大对象,Region中一类特殊的Humongous区域专门存储大对象,只要大小超过了一个Region容量一般的对象就可以判定为大对象。而对于超过了整个Region容量的超大对象,会被存放在连续N个Humongous中,G1把其视为老年代的一部分。

G1收集器的运行过程(mixed GC):
【初始标记】:标记GC Roots能直接关联到的对象,并且修改TAMS指针的值,让下一阶段用户线程并发运行时,能正确地在可用的Region中分配新对象。
【并发标记】:从GC Root开始对堆中对象进行可达性分析,递归扫描整个堆里的对象图,找出要回收的对象。(可与用户线程并发执行)
【最终标记】:处理并发结束后仍遗留下来的最后那少量的STAB记录
【筛选回收】:更新Region的统计数据,对各个Region的回收价值和成本进行排序,根据用户所期望的停顿时间来制定回收计划,可以自由选择任意多个Region构成回收集,然后把决定回收那一部分Region的存活对象复制到空的Region中,再清理掉整个旧的Region的全部空间。

在这里插入图片描述
G1与CMS对比
优势:
·可以制定最大停顿时间、分Region的内存布局、按收益动态确定回收集;
·G1内存不会产生内存空间碎片,垃圾回收后可提供规整的可用内存。
劣势:
·(占用内存更大)处理跨代引用,G1的每个Region都有一个卡表来处理跨代指针,是的G1的记忆集可能会占整个堆容量的20%乃至更多内存空间。
·(额外执行负载)相比起增量更新算法,原始快照搜索能够减少并发标记和重新标记阶段的消耗,避免CMS那样在最终标记阶段停顿时间过长的缺点,但是在用户程序运行过程中确实会产生由跟踪引用变化带来的额外负担。
总而言之:目前在小内存应用上CMS的表现大概率仍然要会优于G1,而在大内存应用上G1则大多能发挥其优势,这个优劣势的Java堆容量平衡点通常在6GB至8GB之间。

内存分配优化

  1. 对象优先分配在Eden代
  2. 大对象直接进入老年代
  3. 长期存活的对象进入老年代
  4. 动态对象年龄判定
    如果在Survivor空间中相同年龄所有对象大小的总和大于Survivor空间的一半,年龄大于或等于该年龄的对象就可以直接进入老年代。
  5. 空间分配担保

虚拟机性能监控、故障处理工具

  1. jps:虚拟机进程状况工具:可以列出正在运行的虚拟机进程,并显示虚拟机执行主类(Main Class,main()函数所在的类)名称以及这些进程的本地虚拟机唯一ID(LVMID,Local Virtual Machine Identifier)。
  2. jstat:虚拟机统计信息监视工具:用于监视虚拟机各种运行状态信息的命令行工具。
  3. jinfo:java配置信息工具:是实时查看和调整虚拟机各项参数
  4. jmap:java内存映像工具:令用于生成堆转储快照(一般称为heapdump或dump文件)。
  5. jstack:java堆栈跟踪工具:用于生成虚拟机当前时刻的线程快照(一般称为threaddump或者javacore文件)。

Class文件

class文件的构成

在这里插入图片描述

类加载过程

java虚拟机把描述类的数据从Class文件加载到内存,并对数据进行校验,转换解析和初始化,最终行测可以被虚拟机直接使用的java类型。
类的加载、连接、初始化过程都是在程序运行期间完成的。

类加载机制

  1. 生命周期
    在这里插入图片描述2.类加载过程
    【加载】:通过一个全限定名来获取定义此类的二进制字节流;将这个二进制字节流的静态存储结构与转化为方法区的运行时数据结构;在内存中生成一个代表这个类的Class对象,作为方法区这个类的这个数据的访问入口。
    【验证】:确保Class文件的字节流中包含的信息符合《java虚拟机规范》的全部约束,保证这些信息不会损害虚拟机自身安全;
    【准备】:正式为类定义的变量(静态变量)分配内存并设置类变量初始值。
    【解析】:将常量池的符号引用替换为直接引用。
    【初始化】:执行类构造器clinit();开始真正执行java代码。

类加载器

【启动类加载器】:负责将存放在JAVA_HOME/lib目录下,或者是 -Xbootclasspath参数所制定的路径中存放的能够被java虚拟机识别的类库加载到虚拟机内存中。
【扩展类加载器】:加载JAVA_HOME/lib/ext目录中,或者java.ext.dirs系统变量所制定的路径中所有的类库。
【应用程序类加载器】:又称为系统类加载器,负责加载用户类路径上所有的类库

双亲委派机制

如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器去完成。只有在父类加载器反馈自己无法完成这个加载请求时,子加载器才会尝试去完成加载。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值