深入理解Java虚拟机(一)知识储备
JVM内存划分:
① 方法区 (线程共享) 常量 静态变量 JIT(即时编译器)编译后代码也在方法区存放
② 堆内存(线程共享) 垃圾回收的主要场地
③ 程序计数器 当前线程执行的字节码的位置指示器
④ Java虚拟机栈(栈内存) :保存局部变量,基本数据类型以及堆内存中对象的引用变量
⑤ 本地方法栈 (C栈):为JVM提供使用native方法的服务
JDK 1.8同JDK 1.7 最大的区别是:元数据取代了永久代.元空间的本质和永久代类似,都是对JVM规范中的方法区的实现.其元空间和永久代之间的最大区别在于:元数据空间不在虚拟机中,而是在本地内存中
详细了解一下各个部分
程序计数器(PC寄存器)
程序计数器的定义: 程序计数器是一块较小的内存空间,是当前线程正在执行的哪一条字节码指令的地址,若当前线程正在执行的是一个本地方法,那么此时程序计数器为Undefined
程序计数器的作用:
字节码解释器通过改变程序计数器来依次获取指令,从而实现代码的流程控制
在多线程情况下,程序计数器记录的是当前线程执行的位置,从而当线程切换回来时,就知道上次线程执行到哪了
程序计数器的特点
是一块较小的内存空间
线程私有,每个线程都有自己的程序计数器
生命周期:随着线程的创建而创建,随着线程的销毁而销毁
是一个唯一不会出现的OutOfMemoryError的内存区域
Java虚拟机栈
定义: 描述Java方法运行过程的内存模型
Java虚拟机栈会为每一个即将运行的Java方法创建一块叫做"栈帧"的区域,用于存放该方法运行过程中的一些信息,如 局部变量表 /操作数栈 /动态链接 /方法出口信息
压栈出栈过程:
当方法运行过程中需要创建局部变量时,就将局部变量的值存入栈帧的局部变量表中
Java虚拟机栈的栈顶是当前正在执行的活动栈,也就是当前正在执行的方法,PC寄存器也会指向这个地址,只有这个活动的栈帧的本地变量可以被操作数栈操作,当前这个栈帧中调用另一个方法,与之对应的额栈帧又会被创建,新创建的栈帧压入栈顶,变成当前的活动栈帧,方法结束后,当前栈帧的返回值变成新的活动栈帧的中的操作数栈的一个操作数,如果没有返回值,那么新的活动栈帧中操作数栈的操作数没有变化
由于Java虚拟机栈是线程对应的,数据不是共享的,因此不用关心数据一致性问题,也不会存在同步锁的问题
特点
局部变量表随着栈帧的创建而创建,他的大小在编译时确定,创建时只需分配事先规定的大小即可,在方法运行的过程中,局部变化表的大小不会发生变化
Java虚拟机栈会出现两种异常:StackOverFlowError和OutOfMemoryError
StackOverFlowError若Java虚拟机栈的大小不允许动态扩展,那么当前线程请求的栈的深度超过当前的Java虚拟机栈的最大深度是,就会抛出此异常
OutOFMemoryError,若允许动态扩展,那么当前线程的请求的栈内存用完了,无法再动态扩展时,抛出此异常
Java虚拟机栈也是线程私有,随着线程创建而创建,随着线程的结束而销毁
本地方法栈(C栈)
定义:
是为了JVM运行native方法准备的空间,由于很多native方法都是用C语言实现的,所以通常又叫C栈,它与Java虚拟机栈实现的功能类似,只不过本地方法栈描述本地方法运行过程的内存模型
栈帧变化过程:
本地方法被执行时,在本地方法栈也会创建一块栈帧,用于存放该方法的局部变量表 /操作数栈 /动态链接 /方法出口等信息; 方法结束后,相应的栈帧也会出栈,并释放内存空间.也会抛出StackOverFlowError和OutOfMemoryError异常
堆
定义:
堆是用来对象的内存空间,几乎所有的对象都存储在堆中
特点:
线程共享,整个Java虚拟机只有一个堆,所有线程都访问同一个堆,在虚拟机启动时创建,是垃圾回收的主要场地进一步可分为:新生代(Eden区 From Survior To Surviror) 老年代不同的区域存放的不同生命周期的对象,这样可以根据不同区域使用不同的垃圾回收算法,更具有针对性. 堆的大小也可以固定也可以扩展,对于主流的虚拟机,堆大小可扩展的,因此当线程请求分配的内存,但堆已满,且内存已无法再扩展,就抛出OutOfMemoryError异常
方法区
定义:
Java虚拟机规范中定义方法区是堆的一个逻辑部分,方法区存放以下信息 已被虚拟机加载的类信息 /常量 /静态变量 /即时编译后代码
特点:
线程共享,方法区是堆的一个逻辑部分,因此和堆一样,都是线程共享,整个虚拟机中只有一个方法区,永久代方法区中的信息一般需要长期存在,而且它又是堆的逻辑分区,因此用堆的划分方法,把方法区称为"永久代",内存回收的效率低.方法区中的信息一般需要长期存在,回收一遍只有少量信息无效.主要回收的目标是: 对常量池的回收;对类型的卸载Java虚拟机规范l对方法区的要求比较宽松,和堆一样,允许固定大小.也允许动态扩展,还允许不实现垃圾回收
运行时常量池:
方法区中存放:类信息 常量 静态变量 即时编译器变编译后代码.常量就存放在运行时常量池中.当类被Java虚拟机加载后.class文件中的常量就存在方法区的运行常量池,而且在运行期间,可以向常量池中添加新的常量,如String类的intern()方法就能在运行期间向常量池中添加字符串常量
直接内存(堆外内存)
直接内存是除Java虚拟机之外的内存,但有可能被Java使用
操作直接内存:
在NIO中引入了一种基于通道和缓存的IO方式,他可以调用本地方法的直接分配Java虚拟机之外的内存,然后通过一个存储在堆中的DirectByteBuffer对象直接操作该内存,而无需将外部内存中数据复制到堆中再进行操作,从而提高数据操作的效率,直接内存的大小不受Java虚拟机,也会抛出OutOfMemoryError异常
直接内存和堆内存比较:
直接内存申请空间耗费更高的性能
直接内存读取IO的性能优于普通的堆内存
直接内存的作用链:本地IO–>直接内存–>本地IO
堆内存的作用链:本地IO–>直接内存–>非直接内存–>直接内存—>本地IO
服务器管理员在配置虚拟机参数时,会根据实际内存设置 -Xmx等参数信息,但经常忽略直接内存,使得各个内存区域总和大于物理内存,从而导致动态扩展时出现OutOFMemoryError
调优基础
jvm相关参数
堆内存分配
① : JVM初始分配的内存由-Xms指定,默认是物理内存的1/64
②: JVM最大分配的内存由-Xmx指定,默认是物理内存的1/4
③: 默认空余堆内存小于40%时,JVM就会增加堆直到-Xmx的最大限制;空余堆内存大于70%时,JVM会减少堆直到-Xms的最小限制
④: 因此服务器一般设置-Xms -Xmx相等以避免在每次GC后调整堆大小. 对象的堆内存由成为垃圾回收器的自动内存管理系统回收
非堆内存分配:
①:JVM使用-XX:PermSize 设置非堆内存的初始值,默认物理内存的1/64;
② :由XX:MaxPermSize设置设置最大非堆内存的大小
③: -Xmn2G :设置年轻代的大小为2G
④ :-XX:SurvivorRatio ,设置年轻代中Eden区与Survivor区的比值
什么对象需要回收
gc不可达的对象(可达性分析)
引用类型
强 Object obje = new Object();
软 SoftReference 实现缓存(图片加载或者比较大的缓存),在内存不足发生gc的时候被回收
弱 在发生gc的时候就会被回收
虚 在引用的时候会被通知
垃圾回收的算法
① 引用计数法:原理是在此对象有个引用,即增加一个计数,删除一个引用则减少一个计数.垃圾回收时,只收集计数为0的对象.此算法的最致命的无法处理循环引用的问题
②: 标记-清除 :此算法分两个阶段,第一阶段从引用的根节点开始标记所有被引用的对象,第二阶段遍历整个堆,把未标记的对象清除,此算法需要暂停应用,同时产生内存碎片
③: 复制回收算法 此算法把内存划分为两个相等的区域,每次只使用一个区域,垃圾回收时,遍历当前使用的区域,把正在使用的对象复制到另一个区域中每次算法每次只处理正在使用的对象,因此复制的成本比较小,同时复制过去以后还能进行相应的内存整理,不会出现"碎片问题",此算法的缺点也很明显,需要两倍的内存空间
④: 标记-整理:此算法结合了"标记-清除"和:复制算法的两个的优点,也是分两个阶段,第一个阶段从根节点开始标记所有被引用对象,第二阶段遍历整个堆,清除未标记的对象并且把存活的对象"压缩"到堆的其中一块,按顺序排放,此算法避免"标记-清除"的碎片问题,同时也避免"复制"的空间问题
垃圾收集器
SWT stop the world
Serial
ParNew
-XX:ParallelGCThreads
Parallel Scavenge (全局)
吞吐量 = 运行用户代码时间 / (运行用户代码时间 + 垃圾收集时间)
-XX:MaxGCPauseMillis=n
-XX:GCTimeRatio=n
-XX:UseAdaptiveSizePolicy GC Ergonomics
Serial Old
CMS备用预案 Concurrent Mode Failusre时使用
标记-整理算法
Parallel Old
标记-整理算法
CMS
标记-清除算法
减少回收停顿时间
碎片 -XX:CMSInitiationgOccupancyFraction
Concurrent Mode Failure 启用Serial Old
-XX:+UseCMSCompactAtFullCollection
-XX:CMSFullGCsBeforeCompaction 执行多少次不压缩FullGC后 来一次带压缩的 0 表示每次都压
-XX:+UseConcMarkSweep
gc root
第一种总结
被启动类(bootstrap加载器)加载的类和创建的对象
JavaStack中引用的对象(栈内存中引用的对象)
方法区中静态引用
第二种总结
判断算法
- 引用计数法
- 可达性分析 GC Roots
- 虚拟机栈中本地变量表引用的对象
- 方法区中
- 类静态变量引用的对象
- 常量引用的对象
- 本地方法栈中JNI引用的对象 - 不可达是不是就一定会被回收
finalize()
对象分配
-XX:SurvivorRatio=8 8:1:1
TLAB Thread Local Allocation Cuffer
直接进入老年代:
对象很大:
-XX:PretenureSizeThreshold=3145728 3M
长期存活的对象
-XX:MAxxTenuringThreshold=15
动态年龄判断
相同年龄所有对象的大小总和 > Survivor空间的一半
分配担保
在发生minor gc的时候,如果要往老年代移对象之前,会去检查,老年代连续可用空间是否大于新生代内存的总空间;如果不大于,则判断是否大于每次minor gc所需要的平均内存大小,满足条件会。
垃圾收集器
Serial(串行收集器)
- 特性:单线程,stop the world,采用复制算法
- 应用场景:jvm在Client模式下默认的新生代收集器
- 优点:简单高效
ParNew
- 特点:是Serial的多线程版本,采用复制算法
- 应用场景:在Server模式下常用的新生代收集器,可与CMS配合工作
Parallel Scavenge
- 特点:并行的多线程收集器,采用复制算法,吞吐量优先,有自适应调节策略
- 应用场景:需要吞吐量大的时候
SerialOld
- 特点:Serial的老年代版本,单线程,使用标记-整理算法
Parallel Old
- Parallel Scavenge的老年代版本,多线程,标记-整理算法
CMS
- 特点:以最短回收停顿时间为目标,使用标记-清除算法
- 过程:
- 初始标记:stop the world 标记GC Roots能直接关联到的对象
- 并发标记:进行GC Roots Tracing
- 重新标记:stop the world;修正并发标记期间因用户程序继续运作而导致标记产生变动的 那一部分对象的标记记录
- 并发清除:清除对象
- 优点:并发收集,低停顿
- 缺点:
- 对CPU资源敏感
- 无法处理浮动垃圾(并发清除 时,用户线程仍在运行,此时产生的垃圾为浮动垃圾)
- 产生大量的空间碎片
G1
- 特点:
- 面向服务端应用,将整个堆划分为大小相同的region。
- 并行与并发
- 分代收集
- 空间整合:从整体看是基于“标记-整理”的,从局部(两个region之间)看是基于“复制”的。
- 可预测的停顿:使用者可明确指定在一个长度为M毫秒的时间片段内,消耗在垃圾收集上的时间不得超过N毫秒。
- 执行过程:
- 初始标记:stop the world 标记GC Roots能直接关联到的对象
- 并发标记:可达性分析
- 最终标记:修正在并发标记期间因用户程序继续运作而导致标记产生变动的那一部分标记记录
- 筛选回收:筛选回收阶段首先对各个Region的回收价值和成本进行排序,根据用户所期望的GC停顿时间来制定回收计划
相关文章推荐
深入理解Java虚拟机(一)知识储备
https://blog.csdn.net/shang_xs/article/details/88122304
深入理解Java虚拟机(二)面向GC的Java编程
https://blog.csdn.net/shang_xs/article/details/88072885
深入理解Java虚拟机(三)GC优化实战
https://blog.csdn.net/shang_xs/article/details/88052906