JVM
2021-01-27 15:34:09 5 举报
JVM
作者其他创作
大纲/内容
对象的内存布局
检查加载
静态变量static
锁标志
虚拟机遇到一条new指令,首先去检查这个指令的参数是否能在方法区的常量池中定位到一个类的符号引用,并且检查这类是否已经被加载,解析,初始化过,若没有,则必须先进行类加载过程。
this
返回地址
内存空间初始化
局部变量表
G1垃圾收集器
可以
每个方法在执行的时候都会创建一个栈帧
类型指针
直接内存(JVM规范里没有规定)
o(对象)
链接
确保被加载的类的正确性。确保Class文件的字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机自身的安全。1、文件格式验证2、元数据验证3、字节码验证4、符号引用验证
并行和并发并行:垃圾收集的多线程的同时进行。并发:垃圾收集的多线程和应用的多线程同时进行。
虚拟机优化计数——逃逸分析逃逸分析逃逸分析是目前JVM中比较前沿的优化技术,它不是直接的优化手段而是为其他优化手段提供依据的分析技术逃逸分析的基本行为就是分析对象动态作用域。逃逸的方式 方法逃逸:在一个方法体内,定义一个局部变量,而它可能被外部方法引用,比如作为调用参数传递给方法,或作为对象直接返回。或者,可以理解成对象跳出了方法。 线程逃逸:这个对象被其他线程访问到,比如赋值给了实例变量,并被其他线程访问到了。对象逃出了当前线程。逃逸分析的好处 如果一个对象不会在方法体内,或线程内发生逃逸(或者说是通过逃逸分析后,使其未能发生逃逸) 1. 栈上分配 一般情况下,不会逃逸的对象所占空间比较大,如果能使用栈上的空间,那么大量的对象将随方法的结束而销毁,减轻了GC压力 2. 同步消除 如果你定义的类的方法上有同步锁,但在运行时,却只有一个线程在访问,此时逃逸分析后的机器码,会去掉同步锁运行。 3. 标量替换 Java虚拟机中的原始数据类型(int,long等数值类型以及reference类型等)都不能再进一步分解,它们可以称为标量。相对的,如果一个数据可以继续分解,那它称为聚合量,Java中最典型的聚合量是对象。如果逃逸分析证明一个对象不会被外部访问,并且这个对象是可分解的,那程序真正执行的时候将可能不创建这个对象,而改为直接创建它的若干个被这个方法使用到的成员变量来代替。拆散后的变量便可以被单独分析与优化,可以各自分别在栈帧或寄存器上分配空间,原本的对象就无需整体分配空间了。牵涉到的JVM参数:-XX:+DoEscapeAnalysis:启用逃逸分析(默认打开)-XX:+EliminateAllocations:标量替换(默认打开)-XX:+UseTLAB 本地线程分配缓冲(默认打开)
CMS垃圾收集器(老年代)——标记清除算法1、初始标记-标记GCRoot(STW)2、并发标记-从GCRoot查找可回收对象,做线性分析3、重新标记-并发标记过程中产生的新垃圾进行重新标记(STW)4、并发清理-开始清理标记的垃圾5、重置线程-清理的对象可能是在用户线程中使用到的线程,所以需要重置用户线程要点:寻求最短暂停时间(更注重性能)缺点:1、CPU资源:并发阶段多线程占据CPU资源,如果CPU资源不足,效率会明显降低2、浮动垃圾:由于CMS并发清理阶段用户线程还在运行着,伴随程序运行自然就还会有新的垃圾不断产生,这一部分垃圾出现在标记过程之后,CMS 无法在当次收集中处理掉它们,只好留待下一次 GC 时再清理掉。由于浮动垃圾的存在,因此需要预留出一部分内存,意味着 CMS 收集不能像其它收集器那样等待老年代快满的时候再回收。在 1.6 的版本中老年代空间使用率阈值(92%)如果预留的内存不够存放浮动垃圾,就会出现 Concurrent Mode Failure,这时虚拟机将临时启用 Serial Old 来替代 CMS。3、内存碎片:标记 - 清除算法会导致产生不连续的空间碎片
单线程收集
并发安全问题用CAS配上失败重试本地线程池分配缓冲TLAB(Eden 1%)
Eden分配
GC (Garbage Collection ) -Xms 堆区内存初始内存分配的大小-Xmx 堆区内存可被分配的最大上限-XX:+PrintGCDetails 打印GC详情-XX:+HeapDumpOnOutOfMemoryError 当堆内存空间溢出时输出堆的内存快照 -Xmn 新生代大小-XX:SurvivorRatio 2个Survivor区和Eden区的比值8表示两个 Survivor: Eden = 2: 8 ,每个 Survivor 占 1/10 GC overhead limit exceeded 超过98%的时间用来做GC并且回收了不到2%的堆内存时会抛出此异常1.垃圾回收会占据资源2.回收效率过低也会有限制为什么 new 出的对象不会被回收了,我们来看看 GC 是如何判断对象的存活Minor GC特点: 发生在新生代上,发生的较频繁,执行速度较快触发条件: Eden 区空间不足\\空间分配担保Full GC特点: 主要发生在老年代上(新生代也会回收),较少发生,执行速度较慢触发条件:1、调用 System.gc()2、老年代区域空间不足3、空间分配担保失败4、JDK 1.7 及以前的永久代(方法区)空间不足CMS GC 处理浮动垃圾时,如果新生代空间不足,则采用空间分配担保机制,如果老年代空间不足,则触发 Full GC
垃圾回收算法与垃圾回收器
优点:内存利用率100%缺点:标记-清除的效率都不高(比对复制算法)会产生大量的不连续的内存碎片
JVM中的垃圾收集器
TLAB分配
1、引用计数法给对象添加一个引用计数器,当对象增加一个引用时计数器加 1,引用失效时计数器减 1。引用计数为 0 的对象可被回收。(Python 在用,但主流虚拟机没有使用)优点:快,方便,实现简单。缺陷:对象相互引用时(A.instance=B 同时 B.instance=A),很难判断对象是否该回收。2、可达性分析(Java中使用)基本思路就是通过一系列的称为“GC Roots”的对象作为起始点,从这些节点开始向下搜索,搜索所走过的路径称为引用链(Reference Chain),当一个对象到 GC Roots 没有任何引用链相连时,则证明此对象是不可用的。GC Roots 的对象包括下面几种:当前虚拟机栈中局部变量表中的引用的对象当前本地方法栈中局部变量表中的引用的对象方法区中类静态属性引用的对象方法区中的常量引用的对象finalize可以完成对象的拯救,但是 JVM 不保证一定能执行,所以请忘记这个“坑”。
对象的访问方式
动态链接(多态)
运行时常量池1、符号引用2、字面量(String a = \"a\";)JDK1.6:运行时常量池在方法区中JDK1.7运行时常量池在堆中
调优原则1、大多数的java应用不需要GC调优2、大部分需要GC调优的,不是参数问题,而是代码问题3、在实际项目中,分析GC情况优化代码比调整GC参数要重要4、GC调优是最后的手段 GC调优的目的1、GC的时间足够小参考性指标Minor GC执行时间不到50msFull GC执行时间不到1s 2、GC的次数足够小参考性指标Minor GC执行不频繁,大于10秒一次Full GC执行频率不算频繁,大于10分钟1次
JVM内存结构
初始化
存储当前线程运行方法所需要的数据、指令、返回地址
CMS垃圾收集器
JVM执行子系统
实例数据
机器的内存64G
程序计数器
成员变量
Mark Word
失败
加载过程完成
偏向时间戳
使用句柄:如果使用句柄访问的话,堆中还会划出一部分内存作为句柄池,句柄中就包含了对象实例和类数据的具体地址,而reference储存的就是句柄地址。优点:reference储存稳定的句柄地址,如果对象地址被移动,那么只需要改变句柄中的指针,reference本身无需修改。缺点:多了一次指针定位的操作,速度较慢。直接指针:reference直接储存了对象的地址。 优点:只需要一次指针定位,速度较快缺点:在对象被移动时,reference也要进行修改。现在采用直接指针居多。
若类加载检查通过后,接下来虚拟机将为新对象分配内存。对象所需的内存在类加载完成之后便可以确定了,那分配内存的方法有两种:指针碰撞、空闲列表。如果内存是规整的,采用指针碰撞。如果内存不是规整的,采用空闲列表。
即时编译后的代码
运行时常量池
GC调优原则
各种引用(Reference)
永久代(JDK1.7)元空间(JDK1.8)JDK1.8:去永久代:使用元空间(font color=\"#ff3333\
对象自身运行数据
线程1
卸载
成功
虚拟机栈
解析
线程私有(指令)
堆内存分配策略
常量final
对象头
Tomcat类加载机制
栈帧(单个方法)
GC标记<年龄>
栈上分配
操作数栈
不可以
被垃圾回收器回收
直接内存100M
标记-整理算法(Mark-Compact)
JDK为我们提供的工具
JVM运行时数据区(内存)
对齐填充并不是必然存在的,因为Java虚拟机规定对象的起始地址必须是8字节的整数倍,所以说如果当前对象长度不满足这个条件,就会使用占位符来进行补全。
堆
为类的静态变量分配内存,并将其初始化为默认值。准备阶段是正式为类变量分配内存并设置类变量初始值的阶段,这些内存都将在方法区中分配。对于该阶段有以下几点需要注意: 1、这时候进行内存分配的仅包括类变量(static),而不包括实例变量,实例变量会在对象实例化时随着对象一块分配在Java堆中。 2、这里所设置的初始值通常情况下是数据类型默认的零值(如0、0L、null、false等),而不是被在Java代码中被显式地赋予的值。 假设一个类变量的定义为:public static int value = 3; 那么变量value在准备阶段过后的初始值为0,而不是3,因为这时候尚未开始执行任何Java方法,而把value赋值为3的putstatic指令是在程序编译后,存放于类构造器<clinit>()方法之中的,所以把value赋值为3的动作将在初始化阶段才会执行。3、如果类字段的字段属性表中存在ConstantValue属性,即同时被final和static修饰,那么在准备阶段变量value就会被初始化为ConstValue属性所指定的值。 假设上面的类变量value被定义为: public static final int value = 3; 编译时Javac将会为value生成ConstantValue属性,在准备阶段虚拟机就会根据ConstantValue的设置将value赋值为3。
内存分配完成后,虚拟机会将分配到的内存空间都初始化为零值。这一步操作保证了对象实例在Java代码中即使不赋初始值,也可以直接使用。
0
类信息
虚拟机栈(当前线程)-Xss 1M
Mark Word主要用来存放对象自身运行时的数据:hashCode,GC标记,锁标记,偏向线程ID等信息。在32位虚拟机的无锁状态下,Mark Word的32bit空间中有25bit用于储存hashCode,4bit用于储存GC分代年龄,2bit用于储存锁标记位,1bit固定为0。
类加载机制
验证
查找并加载类的二进制数据。1、通过一个类的全限定名来获取其定义的二进制字节流。2、将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构。3、在Java堆中生成一个代表这个类的java.lang.Class对象,作为对方法区中这些数据的访问入口。
到这一步,在虚拟机角度,对象的创建工作就已经完成了。而在Java程序角度来看,对象的创建才刚刚开始,因为还没有进行初始化,要进行初始化,比如说通过构造函数对属性进行赋值等工作完成后,一个真正的对象才算完全产生出来。
堆中参数配置: -Xmn20m 表示新生代大小20m(初始和最大)-XX:SurvivorRatio=8 表示Eden和Survivor的比值,缺省为8 表示 Eden:From:To= 8:1:1 堆内存分配策略:1、对象优先在Eden分配;2、大对象直接进入老年代;3、长期存活的对象将进入老年代;4、动态对象年龄判断;5、空间分配担保。
在G1中,还有一种特殊的区域,叫Humongous区域。 如果一个对象占用的空间超过了分区容量50%以上,G1收集器就认为这是一个巨型对象。这些巨型对象,默认直接会被分配在年老代,但是如果它是一个短期存在的巨型对象,就会对垃圾收集器造成负面影响。为了解决这个问题,G1划分了一个Humongous区,它用来专门存放巨型对象。如果一个H区装不下一个巨型对象,那么G1会寻找连续的H分区来存储。为了能找到连续的H区,有时候不得不启动Full GC。
JVM对象
o(引用地址)
优点:内存利用率100%没有内存碎片缺点:标记和清除的效率都不高效率相对标记-清除要低
GC如何判断对象的存活
对象
......
线程2
随线程产生和消亡,不需要过多考虑内存回收问题
本地方法栈
方法区
对象初始化
标记-清除算法(Mark-Sweep)
指向当前线程正在执行的字节码指令的地址(行号)(唯一不会OOM)确保多线程情况下的程序正常执行
内存溢出&内存泄漏
1
元空间=56G大小只受制于机器内存
本地方法栈保存的是native方法的信息,当一个JVM创建的线程调用native方法后,JVM不再为其在虚拟机栈中创建栈帧,JVM只是简单地动态链接并直接调用native方法
为类的静态变量赋予正确的初始值初始化,为类的静态变量赋予正确的初始值,JVM负责对类进行初始化,主要对类变量进行初始化。在Java中对类变量进行初始值设定有两种方式: ①声明类变量是指定初始值 ②使用静态代码块为类变量指定初始值
堆(8G)-Xms 8G-Xmx 32G
G1垃圾收集器1、空间整合,布局上化整为零2、不会产生内存碎片3、可预测的停顿4、跨新生代和老年代5、标记整理算法+化整为零收集过程:1、初始标记-标记GCRoots2、并发标记-根据GCRoots线性分析可回收对象3、最终标记-重新标记可回收对象4、筛选回收-筛选急需回收的对象进行回收G1 GC模式Young GC:主要是对Eden区进行GC,它在Eden空间耗尽时会被触发。Mixed GC:不仅进行正常的新生代垃圾收集,同时也回收部分后台扫描线程标记的老年代分区。Stop The World:垃圾回收过程中会停顿业务线程,待垃圾回收完成后继续质性业务线程。
使用
尝试TLAB分配
老年代分配
是否可直接进入老年代
传统定义:Reference 中存储的数据代表的是另一块内存的起始地址。强引用一般的 Object obj = new Object() ,就属于强引用。(如果有 GCroots 的强引用)垃圾回收器绝对不会回收它,当内存不足时宁愿抛出 OOM 错误,使得程序异常停止。软引用 SoftReference垃圾回收器在内存充足的时候不会回收它,而在内存不足时会回收它。软引用非常适合于创建缓存。当系统内存不足的时候,缓存中的内容是可以被释放的。一些有用但是并非必需,用软引用关联的对象,系统将要发生 OOM 之前,这些对象就会被回收。弱引用 WeakReference垃圾回收器在扫描到该对象时,无论内存充足与否,都会回收该对象的内存。一些有用(程度比软引用更低)但是并非必需,用弱引用关联的对象,只能生存到下一次垃圾回收之前,GC 发生时,不管内存够不够,都会被回收。注意:软引用 SoftReference 和弱引用 WeakReference,可以用在内存资源紧张的情况下以及创建不是很重要的数据缓存。当系统内存不足的时候,缓存中的内容是可以被释放的。实际运用(WeakHashMap、ThreadLocal)虚引用 PhantomReference幽灵引用,最弱,被垃圾回收的时候收到一个通知。如果一个对象只具有虚引用,那么它和没有任何引用一样,任何时候都可能被回收。虚引用主要用来跟踪对象被垃圾回收器回收的活动。
准备
偏向线程ID
对象中真正储存的有效信息,也就是在Java代码中定义的各个字段,包括自己定义的属性和父类中的属性
尝试栈上分配
多线程收集
hashCode
入栈和出栈操作
双亲委派模型
优点:简单高效,不会出现内存碎片缺点:内存利用率低;存活对象较多时效率明显下降
深入分析堆和栈font color=\"#ff0000\
类型指针指向当前对象的类元数据,虚拟机通过这个指针可以得知该对象是哪一个类的实例,也可以得知该对象占用内存的大小。而如果该对象是数组,那么在对象头中还必须有一块用于记录数组长度的数据,因为虚拟机无法从数组的类型指针得知对象的大小。
m-100
复制算法(Copying)
内存溢出原因:程序在申请内存时,没有足够的内存空间内存溢出的几种方式:1、栈溢出2、堆溢出3、方法区溢出4、本机内存直接溢出内存泄漏的原因:程序申请内存后,无法申请已释放的内存内存泄漏的几种原因:1、长生命周期的对象持有短生命周期对象的引用2、连接未关闭3、变量作用域不合理4、内部类持有外部类5、Hash值改变 内存分析根据:MAT浅堆( Shallow Heap )和深堆( Retained Heap )它引用了谁?谁引用了它?incomingoutgoing
把类中的符号引用转换为直接引用。解析阶段是虚拟机将常量池内的符号引用替换为直接引用的过程,解析动作主要针对类或接口、字段、类方法、接口方法、方法类型、方法句柄和调用点限定符7类符号引用进行。符号引用就是一组符号来描述目标,可以是任何字面量。直接引用就是直接指向目标的指针、相对偏移量或一个间接定位到目标的句柄。
设置
对对象头进行初始化。比如说这个实例是哪一个类的实例,hashCode,GC标记,锁标记,偏向线程id等信息。
对象的分配
加载
内存分配
线程共享(数据)
0 条评论
下一页