深入理解JVM 学习笔记

深入理解JVM 虚拟机的学习笔记

看了两遍的JVM许多地方还是似懂非懂

结合书本一些文章重新梳理一下

深入理解jvm这本书确实不错,但感觉有他的局限性。

整理一下核心

讲的很乱,没有重点。

jdk 1.7 和1.8的区别还是非常大的。

总的预览图
在这里插入图片描述

1.类的加载机制

类的加载机制老是不明白,有很多面试题喜欢考类的加载

真的就5个过程

  • 加载

    1. 加载一个类首先通过全限定名找到这个类的二进制字节流
    2. 把字节流的静态结构转换成方法区的运行时结构
    3. 在内存中生成class对象,作为方法区访问的入口,比如反射需要class对象
  • 验证

    就是验证合理性包括

    文件格式,版本号之类的

    元数据,有不有不符合java语言规范的

    字节码,保证一些转换的问题,指令是正确的

  • 准备

    初始化静态成员变量的值为默认值

  • 解析

    符号引用变成直接引用 简单来说就是确定指针

    • 类和接口解析

      解析C类,分两种情况

      1.C 不是数组类型 可能需要加载父类和接口

      2.C 是数组类型,且元素是对象 就先加载这对象如Integer

    • 类方法解析

      1.直接查名字,有就返回引用

      2.没有 在父类中查

      3.在接口和父类接口中查,有的话说明是抽象类

  • 初始化

    初始化成员变量和静态代码块

    结合一下JAVA编程思想中的知识

    执行顺序是

    父类初始化> 子类初始化

    静态变量静态代码块(同级的看书写顺序)初始化 > 普通成员变量(代码块) > 构造器

    看看代码

    package test;
    
    public class Father {
        //------- 这两个顺序只和书写顺序有关
        private static String staticfiled = getStaticfiled();
        static {
            System.out.println("父类->静态代码块");
        }
        // -------
    
        private String field =getNormalfiled();
    
        public String getNormalfiled(){
            System.out.println("父类->普通成员变量初始化");
            return "hah";
        }
        public static String getStaticfiled(){
            System.out.println("父类->静态成员变量初始化");
            return "haha";
        }
        // 普通方法块
        {
            System.out.println("父类->普通方法块");
        }
        public Father(){
            System.out.println("父类->构造函数");
        }
    
    
    }
    
    
    package test;
    
    public class Son extends Father {
        //------- 这两个顺序只和书写顺序有关
        private static String staticfiled = getStaticfiled();
        static {
            System.out.println("子类->静态代码块");
        }
        // -------
    
        private String field =getNormalfiled();
    
        @Override
        public String getNormalfiled(){
            System.out.println("子类->普通成员变量初始化");
            return "hah";
        }
        public static String getStaticfiled(){
            System.out.println("子类->静态成员变量初始化");
            return "haha";
        }
        // 普通方法块
        {
            System.out.println("子类->普通方法块");
        }
        public Son(){
            System.out.println("子类->构造函数");
        }
        // 在子类中main函数 是仅此慢于静态代码块和静态变量
        public static void main(String[] args) {
            System.out.println("在子类中的main主函数");
            Son son = new Son();
        }
    }
    

    看下结果图:
    在这里插入图片描述

    确定顺序:
    在这里插入图片描述

2.类加载器

区分两个类是否同类,是看类的加载器是否相同,及classloader

在这里插入图片描述

双亲委派模型

双亲委派模型就是这样的结构

说双亲其实又不算双亲,因为不是继承关系,而是组合关系

类加载器收到请求加载类,

首先让父类加载器去尝试加载,

父类加载器收到请求不行再交给子类加载器

这个和dns寻址相反,dns查不到ip就交给根dns服务器来查

区别就是加载器父类不行子类上

dns 地方dns服务器不行交给根dns查

这个也是object是所有类 父类的一个原因

值得注意的是,已经加载的类会缓存起来,下次就不用加载使用就行了。

3. JVM运行时内存结构

JVM运行时内存结构和JMM(JAVA内存模型)不同这点要搞清楚

JVM运行时内存结构是JVM中的,JAVA内存模型是研究多线程内存共享的关系

这里我们讨论的是JVM运行时内存结构

在这里插入图片描述
String 面试题 就喜欢考字符串常量池和引用的问题

经典面试题来几个

  1. String str = new String(“11”) 创建了几个字符串

    创建一个或者两个字符串,字符串常量池如果有这个对象就只在堆上创建一个

    否则在堆上创建一个,在字符串常量池中也创建一个。

  2. String 的 intern()方法 返回一个常量池中的一个字符串,如果常量池没有,则加入常量池再返回

4. 对象的内存分配和垃圾回收

垃圾回收主要是回收heap堆中对象

对象内存分配也是分配在heap堆中

回收Eden 区域中的对象 是MinorGC

回收老年代的则是FullGC

怎么样确定一个对象是死的或者说应该回收的呢

  • 1.引用计数法

    只要引用还在就不应该被回收

  • 2.可达性算法

    这个算法像是一个二叉树,从根节点GCroot 向下遍历

    找到的对象则是有用的,没有找到的则是无用

    GCroot对象有

    ​ 栈帧中引用的对象即方法局部变量

    ​ 方法区的静态对象

    方法区常量引用对象

垃圾收集算法

  • 标记清除

    会产生内存空间碎片不好整理

  • 复制(适用于朝生夕死的)

    分成几块,某块用完复制到相同的一块去,然后清除

    在新生代中

    从Eden + from space 到 to space 就是这样一个过程

  • 标记整理(适用于长存活对象)

    标记存活的向一端内存移动

  • 分代(一般都是这样)

每一次清理都需要可达性算法,

那这样就需要所有对象的引用,为了速度,

JVM 使用 OopMap数据结构来知道那里有对象的引用

同时,清理需要暂停所有线程,这样就会需要STW(stop the world)

暂停线程,就会造成安全影响,为了安全,JVM只在安全点stw

完整的GC流程

垃圾收集器

收集器有很多种,需要不同新生代收集器和老年代收集器的组合

  • Serial

    单线程 串行回收 新生代复制 ,老年代标记压缩,会stw

  • ParNew

    Serial的多线程 新生代并行 老年代串行 新生代复制,老年代标记压缩

    参数:

    -XX:+UseParNewGC ParNew收集器

    -XX:ParallelGCThreads 限制线程数量

  • Parallel

    类似Serial,强调吞吐量

  • Parallel old

    老年代,使用标记整理

  • CMS(老年代) 重点 强调响应时间

    4个步骤 concurrent mark sweep

    初始标记(CMS initial mark) 有 STW 标记gcroots能到的对象 时间短

    并发标记(CMS concurrent mark) 搜索gcroot的子节点 时间长

    重新标记(CMS remark) 有STW 时间短

    并发清除(CMS concurrent sweep) 时间长

  • G1 目前最强的 (新生老年都可以)

    标记整理,没有内存空间碎片,新生代和老年代不一定内存上连续

    划分堆为相同大小的 Region

    可以预测停顿时间

    清理过程和CMS差不多

    5个步骤

    1.标记 触发minorGC

    2.Region区扫描

    3.整个堆并发扫描

    4.再标记 有stw

    5.并发清除 有stw

发布了22 篇原创文章 · 获赞 2 · 访问量 881

猜你喜欢

转载自blog.csdn.net/weixin_41685373/article/details/105037810