美文网首页
面试题|Java|JVM

面试题|Java|JVM

作者: 萌新CAT | 来源:发表于2020-07-12 10:32 被阅读0次

    Jvm数据区

    [图片上传失败...(image-cd876b-1594521117849)]

    • 线程共享区
      • 方法区(运行常量池)
        主要用于存放堆内存中的一些执行逻辑和变量,类的加载信息,常量,静态变量等一系列信息,和堆不同,方法去不需要频繁gc等操作,又叫永久代

      • Java中的类实例,数组都将存放在堆中,gc发生也是在堆中,堆是Jvm内存中最大的一块内存地址
    • 线程私有区
      • 虚拟机栈
        虚拟机栈就是线程所私有Java方法的内存区域,包括方法所引用的基本数据类型和引用。
        Java方法的使用就是压栈过程,基本数据类型和引用则是存在栈帧中,当方法抛出异常或结束则是出栈的操作。
      • 本地方法区
        native修饰的非Java方法
        本地方法栈也有自己的栈帧等,栈帧里面也相应的有局部变量表,操作数栈,动态链接,出口信息等
      • 程序计数器
        Jvm的本质是多线程交替的,但是如何保证每一个线程拿到cpu资源的时候可以从结束的地方接着开始,就需要程序技术器来保证

    堆的分类

    • Eden
    • Survivor0
    • Survivor1
    • 老年代

    首先分配内存到Eden区,当发生Gc之后转移到s0或者s1中(如果s0先被使用,那么下一次则使用s1,总之是为了保留新生代的内容),每一次gc都会给新生代数据记录次数,一般来说超过15次则进入老年代

    运行常量池

    • 字符串内容
    • final修饰的关键字
    • 基本数据类型的值
    • 符号引用
      • 类和结构完全限定名
      • 字段名称和描述符
      • 方法名称和描述符

    Java对象的创建过程

    1. 类加载
    2. 分配内存
    3. 初始化零值
    4. 设置对象头
    5. 执行init

    类加载过程

    类加载过程图
    1. 加载 (所需类的加载过程)

      • 通过类的全限定名获取该类的二进制流(通过class全限定名从本地,网络,专有数据库中的jar或者zip中获取.class文件)
      • 生成java.lang.class对象作为方法区进入该对象的入口
      • 将字节流的存储结构转化到jvm方法区中运行时的数据结构
    2. 验证 (了解Class字节文件是否符合当前虚拟机要求)

      • 文件格式验证: 字节流是否符合class文件规范
      • 元数据验证: 是否符合java的语法规范,例如继承接口,是否实现了接口中的方法
      • 字节码验证: 数据和控制流验证,保证方法中的类型转换有效
      • 符号引用验证: 验证是否可以通过符号引用找到相应的对象和变量
    3. 准备 (为类的变量分配内存和设置类的初始值(即方法区分配这些变量空间))

      • 为变量分配空间
      • 为变量初始化赋值的过程,例如int 赋值为 0 ,对象赋值为 null 等
      • 特殊 private static final a = 1
        正常来说,应该是初始化阶段赋值,但是这个情况下直接在方法去中替换a = 1 ,则在准备阶段就完成赋值
    4. 解析 (虚拟机将常量池中的符号引用转为直接引用的过程)

      • 什么是符号引用?
        用一串不会有歧义的符号来标识引用的对象或者是变量
      • 什么是直接引用?
        正式用指针去引用符号和变量
    5. 初始化

    真正按照程序员的意愿去初始化值

    分配内存

    分配方法
    1. 指针碰撞
      内存空间比较整洁,直接移动指针分配空间
    2. 空闲链表
      “见缝插针”,空间不连续,则找到空闲的地方插入数据
    线程安全问题
    1. 利用CAS不都安的尝试获取内存空间直到成功(目前虚拟机的解决方案)
    2. TLAB: 为每个线程独自分配一部分空间,且独有,分配的时候优先分配到该空间中

    设置对象头

    例如Synchronized关键字需要Mark Word中的monitor对象(MonitorExit,MonitorEnter)

    对象的访问方式

    1. 句柄访问


      image

      reference -> 句柄池中指针 -> 实例数据

    2. 直接访问


      image

      reference -> 实例数据

    3. 句柄和直接访问的优缺点分析

    论访问速度直接访问最快,但是如果需要删除的话需要直接删除数据
    句柄中则可以直接把句柄值赋null,效率更快

    对象死亡的分析方法

    1. 程序计数法 : 清零以后则可以判断死亡
    2. 可达性分析法 : 以Gc root为起点 看看各个对象是否可以连接起来,如果连接不起来,被独立则可以判断回收
    • 什么是Gc root(GC Roots一般在JVM的栈区域里产生)
      • 处于激活状态的线程
      • 栈中的对象
      • JNI中的变量
      • JNI的全局引用
      • 对象头的Monitor对象

    引用的类型

    • 强引用 (大部分是强引用,虚拟机就算oom,也不会回收引用)
    • 软引用 (当内存不足的时候,会回收软引用)
    • 弱引用 (只要被回收器判定为垃圾,则直接回收,可以用于判断是否被gc回收)
    • 虚引用 (虚引用不在乎引用了什么对象,可以说是一种gc标志,必须配合引用队列,可以在对象被回收之前做一些操作)

    垃圾回收算法

    • 标记清除算法(标记为垃圾后,直接删除)
    • 复制算法 (开辟两块空间,直接将为被回收的部分复制到另一部分的空间内)
    • 标记整理算法 (先标记,之后移动,使得内存空间干净)
    • 分代收集算法 (分为年轻代和老年代,分别使用不同的算法,新生代用复制算法, 老年代用标记整理算法)

    垃圾回收器

    • Serial (单线程)

    单线程回收垃圾,回收的时候需要暂停其它的一切线程

    • Parnew (多线程)

    对Serial的升级,其它不变,就是变成多线程

    • Parallel Scavenge (多线程)

    提升吞吐量(程序运行时间/CPU使用时间),提高回收次数,减少回收时间

    • CMS (真正意义上的多线程)
      • 标记清除算法
      • 过程
        1. 初始标记 (暂停所有线程,标记Gc Root相连的对象)
        2. 并发标记 (进行可达性分析,标记一系列回收对象)
        3. 重新标记 (修正并发标记期间用户的修改)
        4. 并发清除 (开始清理)
      • 缺点
        • 对CPU资源敏感
        • 无法清理浮动垃圾
        • 有太多的空间碎片
    • G1 (多线程,不区分新生代和老年代)
      • 预览图


        image
      • 分区 Region (G1对内存的使用以分区(Region)为单位,而对对象的分配则以卡片(Card)为单位。)

      不区分老年代和年轻代都会直接被划分成Region,每个Region还会被细分为若干个大小为512 Byte的Card,卡片还会记录在全局卡片表(Global Card Table)中.
      不同卡中的对象可能会互相引用,还可能是跨域Region引用,如果存在的话会直接写屏障(并发标记阶段)
      + 分区模型
      - 预览图

      image
      - 巨型对象 Humongous Region
      由于太大会导致分区出现问题,因此直接在老年代中分配空间
      - 已记忆集合 Remember Set (RSet) (存在于Region中)
      - 记录引用分区内对象的卡片索引
      - 内部使用Per Region Table (PRT) 记录使用引用的情况(稀少,细粒度,粗粒度)
      + 稀少 (记录到卡)
      + 细粒度 (记录到region)
      + 粗粒度 (记录到引用的数目)
      - 过程
      1. 初始标记 (GC Root的标记)
      2. 并发标记 (三色标记法开始)
      3. 最终标记 (三色标记的最终标记)
      4. 筛选回收 (复制清除算法)
      - 三色标记
      + white (不可达回收)
      + grey (子对象还未扫描完)
      + black (gc root)
      - STAB
      > 本质是上对对象的一次快照,快照的内容就是查看对象的颜色(三色标记)以维持并发的效率。

    双清委派模型

    采用双亲委派模式的是好处是Java类随着它的类加载器一起具备了一种带有优先级的层次关系,通过这种层级关可以避免类的重复加载,当父亲已经加载了该类时,就没有必要子ClassLoader再加载一次。
    而有了双亲委派模型,黑客自定义的java.lang.String类永远都不会被加载进内存。因为首先是最顶端的类加载器加载系统的java.lang.String类,最终自定义的类加载器无法加载java.lang.String类

    双亲委派模型的破坏 (借鉴CSDN博主的文章说的很完整了)

    原生的JDBC中Driver驱动本身只是一个接口,并没有具体的实现,具体的实现是由不同数据库类型去实现的。例如,MySQL的mysql-connector-.jar中的Driver类具体实现的。 原生的JDBC中的类是放在rt.jar包的,是由启动类加载器进行类加载的,在JDBC中的Driver类中需要动态去加载不同数据库类型的Driver类,而mysql-connector-.jar中的Driver类是用户自己写的代码,那启动类加载器肯定是不能进行加载的,既然是自己编写的代码,那就需要由应用程序启动类去进行类加载。于是乎,这个时候就引入线程上下文件类加载器(Thread Context ClassLoader)。有了这个东西之后,程序就可以把原本需要由启动类加载器进行加载的类,由应用程序类加载器去进行加载了。
    ————————————————
    版权声明:本文为CSDN博主「Jack老师」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
    原文链接:https://blog.csdn.net/luoyang_java/article/details/92598142

    参考文章

    相关文章

      网友评论

          本文标题:面试题|Java|JVM

          本文链接:https://www.haomeiwen.com/subject/yxjgcktx.html