JVM

内存区域

这里写图片描述

  • 运行时数据区域

    • 线程隔离的区域(生命周期与线程相同)
      • 程序计数器(Program Counter Register)
        • 占比较小的内存空间
        • 当前锁执行字节码的行号指示器
        • 当线程执行java方法时,记录的是正在执行的字节码指令地址.若当前执行Native方法,则为undefined.
        • 唯一一个JVM中没有规定OutOfMemoryError的区域
      • 栈(Stack)(java8后将两者合在一起)
        • 虚拟机栈(VM Stack)
          • java方法执行的内存模型,每个方法执行的同时都会创建一个栈帧(Stack Frame)
          • 局部变量表: 存放编译器可知的各种基本数据类型\对象引用类型\returnAddress类型(指向一条字节码指令的地址\函数返回地址). long\double占两个局部变量空间slot,其余占一个slot. 局部变量表所需内存空间在编译器确定,不可动态改变.
        • 本地方法栈(Native Method Stack)
          • 为虚拟机提供Native方法服务
          • 本地方法: 当某个线程调用本地方法是就不再受虚拟机限制.本地方法通过本地方法接口来访问运行时数据. 本地方法接口都会使用某个本地方法栈, 当线程调用本地方法时,虚拟机会保持java栈不变.当调用java方法时,线程会保存本地方法栈,再进入java栈.
    • 所有线程共享区域
      • 方法区(Method Area)
        又称”Non-Heap”
        永久代实现方法区,随着java8永久代的取消,方法区的概念还留着.
      • 堆(Heap)
        • 储存在本地内存(物理内存)(Native Memory)
        • JVM内存最大的一块,在虚拟机启动是被创建
        • 存放
          • 对象实例(几乎所有对象实例/数组都在这被分配内存,随着JIT的发展一些实例不在这个区域分配内存)
          • 1.7前在永久区的数据
            • interned strings(有说字符串常量池的,有说字面量的.但是从Heap线程共享看,应该是字符串常量池)
              字符串常量池(String Constant Pool): 是一个StringTable类(一个默认1009长Hash表)
              [所有线程共享]
              字面量(Literal): 文本字符串,八种基本类型的值,被声明为final的常量
              [注:这是class常量池的一部分,常量池是在编译期确定的.每个class文件都有一个class常量池]
        • 分类
          • 新生代(Young)
            • Eden: 8,用来接受new/newInstance等方法创建的对象(除非对象超过-XX:PretenureSizeThresold会被分配到Old)
            • From Survivor: 1
            • To Survivor: 1,空的
          • 年老代(Old): 经过多次MinorGC后存活下来的对象.
    • 元空间(MetaSpace)
      • 存放类的元信息(方法数据,方法信息,运行时常量池,符号引用)
      • 储存在本地内存(物理内存)(Native Memory)
      • 与Heap不相连,但共享物理内存
      • 推荐文章: https://www.cnblogs.com/duanxz/p/3520829.html
  • 直接内存

    • NIO类/Native方法会使用这些内存空间
    • 应该是与本地内存一个概念,可能是在1.7之前的说法

GC垃圾收集

  • 判断对象能否回收

    • 引用计数法

      • 原理: 当一个地方引用这个对象,计数器+1,当引用失效-1.为0时回收
      • 缺点: 循环引用无法被回收
      • 引用

        • 强引用(StringReference): 关联对象不会被回收

          Object object = new Object();
        • 软引用(SoftReference): 关联对象会在内存不够时标记,第二次标记会被回收
          可以加速Jvm对垃圾内存的回收速度,维护系统安全,防止内存溢出

          SoftReference<Object> sr = new SoftReference(obj, referenceQueue);
        • 弱引用(WeakReference): 关联对象会在下次GC时回收

          WeakReference<Object> wr = new WeakReference(obj, referenceQueue);
        • 虚引用(PhantomReference): 不会有实体对象,只会在关联对象被GC回收时发出通知.
          用来跟踪对象被垃圾回收的活动必须和引用队列联合使用

          PhantomReference<Object> pr = new PhantomReference(obj, referenceQueue);
    • 可达性分析算法
      • 原理: 通过GC Roots作为起点进行搜索,所有能够到达的对象都是存活的,不可达对象回收.
      • GC Roots
        • 存放在 GC Root Set中
        • 什么是Root: 栈中引用的变量, 方法区/元空间 中 静态属性/常量 引用的对象
      • 两次标记才回收: 当可达性分析法中不可达对象选出后,进行对此标记并筛选(是否有必要执行finalize()方法),当对象没有覆盖finalize()方法/已经调用finalize()方法则视为没有必要. 余下的放在一个队列中进行第二次标记,除非这个对象与GC Roots Set上任何一个对象链接,否则就回收
  • 垃圾回收算法
    • 复制
      • 将内存划分为大小相等的两块,每次只使用其中一块,当这一块内存用完了就将还存活的对象复制到另一块上面,然后再把使用过的内存空间进行一次清理.
      • 缺点: 只是用了一半的内存
    • 标记-清除
      • 将需要回收的对象进行标记,然后清理掉被标记的对象.
      • 缺点: 效率不高, 产生大量不连续的内存碎片
    • 标记-整理
      • 让所有存活的对象都向一端移动,然后直接清理掉端边界以外的内存.
    • 分代
      • 新生代使用:复制算法
      • 老年代使用:标记 - 清理 或者 标记 - 整理 算法
  • 垃圾回收器
    • 新生代
      • Serial
        • 串行
        • 单线程
        • Client
      • ParNew
        • 就是多线程版本的Serial
        • Server首选(能与CMS配合)
      • Parallel Scavenge
        • 关注吞吐量(高效率利用CPU)
        • 多线程
        • 并行
    • 老年代
      • CMS(Concurrent Mark Sweep)
        • 并发
        • 标记-清除
        • 流程
          • 初始标记:标记GC Roots能关联到的对象,会发生停顿
          • 并发标记:在 GC Roots Tracing (即找关联对象),耗时最长,无停顿
          • 并发预清理: 处理在并发标记阶段新进入老年代的对象.
          • 重新标记:修正在并发阶段导致标记变动的一部分,会发生停顿
          • 并发清除:不需要停顿
          • 并发重置: 重置CMS收集器的数据结构,等待下次回收.
        • 缺点
          • 吞吐量低:低停顿的代价
          • 无法处理浮动垃圾,可能出现 Concurrent Mode Failure :浮动垃圾即指在回收过程中产生的垃圾只能在下一次GC中回收,需要在这次GC中留下一部分空间给浮动垃圾,如果所留空间不够存放浮动垃圾就会出现Concurrent Mode Failure.
          • 标记-清除算法导致大量碎片空间,而位置又在年老代会有较多大内存对象需要分配,不得不触发Full GC
            这里写图片描述
      • Serial Old
        • 串行
        • 单线程
        • 标记-整理
        • 作为 CMS 收集器的后备预案,在并发收集发生 Concurrent Mode Failure 时使用.
      • Parallel Old
        • 并行
        • 多线程
        • 标记-整理
        • 在注重吞吐量以及 CPU 资源敏感的场合,都可以优先考虑 Parallel Scavenge 加 Parallel Old 收集器.
    • 新生代+年老代回收器
      • G1(Garbage-First)
        • 并发
        • 多线程
        • 流程
          • 初始标记
          • 并发标记
          • 最终标记:修正在并发阶段产生变化的记录,记录在Remembered Set Logs中,在最终阶段会将Remembered Set Logs的数据合并到Remembered Set中,需要停顿
          • 筛选回收:对Region用回收价值和成本排序,根据用户期望的GC停顿来制定回收计划
        • 特点:
          • G1 把新生代和老年代划分成多个大小相等的独立区域(Region),新生代和永久代不再物理隔离.
          • Region:将一块完整内存空间划分为多个小空间,每个小空间能单独进行垃圾回收
          • Remembered Set:每个Region都有一个,用来记录该Region的引用对象所在的Region,用于可达性分析
        • 优点
          • 空间整合:基于标记整理实现的收集器,从单个Region中是基于复制算法
          • 可预测的停顿:能指定一个长度Mms的时间段中,消耗在GC上的时间不超过Nms

内存分配与回收策略

  • Minor GC
    • 发生在新生代上,因为新生代对象存活时间很短,因此 Minor GC 会频繁执行,执行的速度一般也会比较快.
    • 空间分配担保: 在发生Minor GC前会检查年老代中最大连续内存空间是否大于新生代所有对象总大小,若成立则顺利进行,若不成立会查看HandlePromotionFailure是否允许失败,如果时允许的话就会再检查最大连续空间是否大于所有对象平均值,如果大于则尝试进行Minor GC(有风险),如果不允许则进行一次FullGC
    • 触发条件
      • 当Eden区满时进行
  • Full GC
    • 发生在老年代上,老年代对象和新生代的相反,其存活时间长,因此 Full GC 很少执行,而且执行速度会比 Minor GC 慢很多.
    • 触发条件
      • 调用System.gc(),可通过-XX:DisableExplicitGC 来禁止
      • 老年代空间不足:会进行Full GC,若进行Full GC后空间还是不足则抛出Java.lang.OutOfMemoryError
      • 空间分配担保失败
      • CMS中抛出Concurrent Mode Failure,会触发Full GC
  • 方法区
    • 废弃常量加载过程,里面执行了哪些操
    • 无用类
      • 所有实例被回收
      • 该类的ClassLoader被回收
      • 对应的java.lang.Class对象没有被任何地方引用(不能反射)
  • 策略
    • 对象优先(上限:-XX:PretenureSizeThresold)进入Eden区分配,若Eden区没有足够空间则发生一次Minor GC
    • 若对象大于-XX:PretenureSizeThresold,则直接进入老年代
    • 当survivor区中年龄大于-XX:MaxTenuringThreshold则进入老年代(当同一年龄的对象总和大于survivor区一半时,大于等于此年龄的对象都直接进入老年代)

类文件结构

  • 类文件结构

    • 一组以8为字节为基础的二进制字节流
    • 中间没有分隔符
    • 当遇到8位以上空间的数据时,会按照高位在前的方式分割成若干个8位字节进行存储
    • 采用类似C的结构体的为结构体, 有两种数据类型: 无符号数, 表
    • 规定的长度,先后顺序不允许改变
      这里写图片描述
  • 字节码指令

    • 加载和存储指令: 用于栈帧中局部变量表和操作数帧之间传输
    • 运算指令: 对操作数帧上的值进行特定运算, 并把结构存回操作数帧顶部
    指令作用指令
    加法iadd, ladd, fadd, dadd
    减法isub, lsub, fsub, dsub
    乘法imul, lmul, fmul, dmul
    除法idiv, ldiv, fdiv, ddiv
    求余irem, lrem, frem, drem
    取反ineg, lneg, fneg, dneg
    位移ishl, ishr, iushr, lshl, lshr, lushr
    按位或ior, lor
    按位与iand, land
    按位异或ixor, lxor
    局部变量自增iinc
    比较dcmpg, dcmpl, fcmpg, fcmpl, fcmpd
    • 类型转换指令: 将两种不同的数值类型进行相互转换,小范围到大范围无需显示的转换指令
    • 对象创建和访问: 类实例与数组的创建和操作使用不同的字节码指令
    指令作用指令
    创建类实例new
    创建数组newarray, anewarray, multianewarray
    访问类字段/实例字段getstatic, putstatic, getfield, putfield
    把数组元素加载到操作数栈baload, caload, saload, iaload, laload, faload, daload, aaload
    把操作数栈的值存储到数组元素bastore, castore, sastore, iastore, lastore, fastore dastore, aastore
    取数组长度arraylength
    检查类实例类型instanceof, checkcast
    • 操作数栈管理: 直接操作操作数栈
    指令作用指令
    将操作数栈的栈顶(1/2)个元素出栈pop, pop2
    复制栈顶(1/2)个元素并将(1/2)倍的复制值入栈dup, dup2, dup_x1, dup2_x1, dup_x2, dup2_x2
    将栈顶两个元素互换swap
    • 控制转移: (有/无)条件的修改PC寄存器(存放当前指令)的值
    • 方法调用和返回: 调用指令与数据类型无关, 返回指令根据返回值的类型区分
    指令作用指令
    调用对象的实例方法invokevirtual
    调用接口方法invokeinterface
    调用需要特殊处理的实例方法invokespecial
    调用类方法invokestatic
    调用运行时动态解析出调用点限定符使用的方法invokedynamic
    • 异常处理: java中不是由字节码实现,而是采用异常表的方式
    • 同步: 支持方法级的同步和方法内部指令序列的同步,有监控器(Minitor)实现

类加载机制

  • 概念: 虚拟机把描述类的数据从class文件加载到内存, 并对数据进行校验,转换解析和初始化.
  • 动态加载/连接的实现: 基于java中类型的加载连接和初始化都是在程序运行期间完成的
  • 类初始化时机
    • new实例化对象, 读取/设置静态字段, 调用静态方法
    • 使用反射调用时, 若类没有初始化
    • 初始化一个类时,父类还没有初始化(接口初始化时, 父接口用到时再初始化)
    • 虚拟机启动时, 先初始化主类(包含main()方法的那个类)
    • 使用动态语言支持
    • 注意: 有三种被动引用不会初始化
      • 通过子类调动父类静态字段,子类不会初始化
      • 通过数组定义类的数组, 该类不会初始化
      • 调用类的常量字段,该类不会初始化

类加载器

  • 启动类加载器(Bootstrap ClassLoader)
    有C++语言实现,是虚拟机的一部分.这个类加载器负责存放在\lib目录下/被-Xbootclasspath参数指定路径中的,并被虚拟机识别的类库,将其加载到虚拟机内存中
  • 其他类加载器(都继承与java.lang.ClassLoader)

    • 扩展类加载器(Extension ClassLoader)
      由sun.misc.Launcher$ExtClassLoader实现,他负责加载\lib\ext目录中/被java.ext.dirs指定路径中的类库.开发者可以直接使用该加载器
    • 应用程序类加载器(Application ClassLoader)
      由sun.misc.Launcher#AppClassLoader实现.由于这个类加载器是ClassLoader中的getSystemClassLoader()方法的返回值,所以也叫系统类加载器.负责加载用户类路径(ClassPath)上指定的类库,开发者可以直接使用
  • 双亲委派模型

    • 概念
      要求除了顶层的启动类加载器外,其余的类加载器都应该有自己的父类加载器.(父子关系通常是通过组合关系而不是继承)
    • 工作流程
      类加载器收到类加载请求,先把请求委派给父类加载器去完成(最终都应该传送到启动类加载器), 父类加载器无法完成加载请求才会交给子类去尝试
    • 优点
      java类随着它的类加载器具备一种带优先级的层次关系

类加载过程
内存泄漏的原因
JVM常用操作

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值