• jvm学习


    一、常量池

    1.关于String和Integer的常量池相关

    参考文章

    疑问:

    1.字符串常量有字符串常量池存放,基本类型有包装类常量池存放,运行时常量池到底存放什么
    2.像实例变量,(eg:int i =5)存放在那个地方
    3.字符串常量池共享吗

    2.常量池的分类 参考文章

    class常量池
    运行时常量池
    字符创常量池
    基本类型包装类常量池

    2.1class常量池(非运行时常量池)

    主要存放字面量和字符引用,存在于java编译后的二进制字节码中,以“静态的储存结构”储存在二进制字节码中

    字面量

    1.字面量就是文本字符串的值:eg:String s = “abc” 中的abc。
    字符引用

    1.类和接口的全限定名
    2.字段的名称和描述符
    3.方法名的名称和描述符

    2.2运行时常量池

    主要存放:字面量和符号引用: 参考文章
    符号引用包括:
    类和接口的全限定名
    字段名称和描述符
    方法的名称和描述符

    存放于方法区中
    运行时常量池就是将编译后二进制字节码中的“静态存储结构”(class常量池)转化为“运行时数据结构”。
    在java二进制字节码文件进入jvm的过程中:加载,链接(验证,准备,解析),初始化,使用,卸载。

    加载时主要做三件事
    (1)通过一个类的全限定名来获取此类的二进制字节流
    (2)将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构
    (3)在内存中生成一个类对象,代表加载的这个类,这个对象是java.lang.Class,它作为方法区这个类的各种数据访问的入口。

    需要注意

    (1).生成的Class类和普通类不一样,是由jvm生成单例,作为和外界交互的入口
    (2)多个类可以共用一个运行时常量池,
    (3)在转化为运行时常量池的时候,会将符号引号直接转化为“直接引用”,生成一个地址指向创建好的实例
    (4)并非所有的常量都来自于class常量池中,也可通过代码自动生成
    eg:String.intern()。此方法会先去运行时常量池中找是否有Sring字符串,如果没有会自动生成存放于方法区的常量池中

    2.3字符串常量池

    字符串常量池和运行时常量池存放的位置:详情

    (1)当jvm转化class常量池的时候,eg:String s = “abc” 会把该字符串的引用存放到“字符串常量池”中,然后再堆中创建一个abc对象,
    (2)当jvm在加载java二进制字节码的时候,eg:String str = “yanpengrong” 首先虚拟就会在字符串常量池中通过equals(“yanpengrong”)在堆中去找,如果找到返回yanpengrong所对应的引用地址(在字符串常量池中),如果找不到就在堆中创建一个yanpengrong对象,然后返回一个引用地址给字符串常量池。所以当有多个String str = "yanpengrong"时,其对应在堆中,只有一个对象。

    2.4包装类常量池

    1.对八种基本数据类型(Float,Doudle除外)其范围在-128-127之间的数有缓存技术(常量池)

    二、双亲委派机制 参考文章

    1.正常的Java.class文件被加载
    2.jvm提供了三种类加载器:

    1.BootstrapClassloader:主要加载核心类库,并构造ExtClassloader和AppClassloader
    2.ExtClassloader:主要加载一些扩展的类库
    3.AppClassloader:主要加载程序的主函数类

    3.加载过程

    在这里插入图片描述
    从上图中我们就更容易理解了,当一个Hello.class这样的文件要被加载时。不考虑我们自定义类加载器,首先会在AppClassLoader中检查是否加载过,如果有那就无需再加载了。如果没有,那么会拿到父加载器,然后调用父加载器的loadClass方法。父类中同理也会先检查自己是否已经加载过,如果没有再往上。注意这个类似递归的过程,直到到达Bootstrap classLoader之前,都是在检查是否加载过,并不会选择自己去加载。直到BootstrapClassLoader,已经没有父加载器了,这时候开始考虑自己是否能加载了,如果自己无法加载,会下沉到子加载器去加载,一直到最底层,如果没有任何加载器能加载,就会抛出ClassNotFoundException。那么有人就有下面这种疑问了?

    4.使用双委派的目的

    为了防止有人篡改源代码,当加载“被篡改的类”时首先会去查看有没有被加载,如果没有就会让父类加载器加载,知道BootstrapClassLoader加载,此时会发现,被篡改的类已被加载过,所以就避免了被更改的风险。

    三、运行时数据区

    1.本地方法栈和程序计数器

    2.方法区

    3.栈和堆(栈管运行,堆管储存)

    虚拟机栈
    (1)对于栈来说不需要垃圾回收,因为当方法执行完就会自动回收,所以栈的生命周期和线程一样
    (2)当线程请求的深度大于栈的最大深度时就会发生“StackOverflowError ”错误
    (3)jvm也可以动态向本地申请内存,当申请的内存达到最大时就会发生"OutofMemoryError"
    (4)8种基本类型的变量+对象的引用变量+实例方法都是在栈里面分配内存。
    (5)对于某些方法来说,如果有返回值的话,属于变量逃逸,逃逸之后的变量会存放于堆中

    虚拟机堆
    (1)JVM内存会划分为堆内存和非堆内存,堆内存中也会划分为年轻代和老年代。而非堆内存则为永久代,在jdk1.8之后换成了将永久代换成了元空间,元空间不在jvm中,而在本地内存。
    在这里插入图片描述
    (2)堆内存详解:参考文章
    改文章主要介绍了堆内存模型的分配和相关垃圾回收的机制
    (3)几种引用的类型概念:
    强引用 不能被gc回收
    软引用 当内存不够时,发生gc,优先被回收
    弱引用 gc发生时,立刻被回收
    虚引用 创建一个虚引用对象后,返回的都是一个null,用来检测gc发生情况
    (4)简述一下触发GC的机制,GC的种类,达到GC的条件
    在年轻代中的对象被Minor GC 回收15次以后,会将此类对象存入老年代,当老年代中的内存被占满后会触发 full GC,当该GC执行时会将所有的线程停掉,

    4.垃圾回收机制

    1.引用计数算法:
    2.可达性分析算法

    5.垃圾回收算法

    1.标记-清理算法
    2.复制算法
    3.标记-整理算法
    4.分带收集算法

    四.补充

    1.程序计数器的作用

    1.通过改变程序计数器中的位置地址来依次执行指令
    2.当在多线程的情况下,涉及到线程的切换,通过程序计数器来记录线程切换时的位置,当线程再次运行时能够知道运行在哪儿了
    注意:程序计数器是唯一一个不会出现 OutOfMemoryError 的内存区域
    它的生命周期随着线程的创建而创建,随着线程的结束而死亡。

    2.两种错误类型

    这里是引用

    3.虚拟机栈中的包含的东西及分别代表什么

    局部变量表:
    操作数栈:
    动态链接:
    方法返回地址:

    4.方法区中

    4.1方法区中包含的东西

    类信息、字段信息、方法信息、常量、静态变量、即时编译器编译后的代码缓存等数据

    4.1方法区永久代/元空间设置参数

    这里是引用

    5.运行时常量池在方法区中

    当无法申请到内存时会报OutofMemoryError错误

    6.字符串常量池

    6.1字符串常量池的作用

    是 JVM 为了提升性能和减少内存消耗针对字符串(String 类)专门开辟的一块区域

    6.2字符串常量池的位置

    1.7之前在方法区中
    1.7之后在堆中

    6.2字符串常量池移入堆中的目的

    主要是因为永久代(方法区实现)的 GC 回收效率太低,只有在整堆收集 (Full GC)的时候才会被执行 GC。Java 程序中通常会有大量的被创建的字符串等待回收,将字符串常量池放到堆中,能够更高效及时地回收字符串内存。

    7.描述多线程下的程序计数器的使用(寄存器)

    解释器:扫描二进制文件
    寄存器:程序计数器的物理实现
    即时编译器(JIT):用来存放热点代码

    五.垃圾回收

    1.执行GC的条件 参考文章在这里插入图片描述

    在Eden区中执行一次Minor GC后如果有存活的对象,就会被放入survivor中,当新生代中对象被Minor GC15次后进入老年代,

    2.如何判断一个对象是否存活

    1.引用技术法:确定当有对象互相引用是不能被回收
    2.可达性分析法:
    什么样的对象可作为可达性分析法的对象:
    在这里插入图片描述

    3.对象判定可回收后就一定能回收吗?

    不一定
    1.需要标记两次才可回收
    2.如果该对象没有finalize(),或已经执行过finalize()方法也会不被回收

    4.如何判断一个产量是废弃常量!

    在字符串常量池中没有任何关于常量对象的引用

    5.如何判断一个类时废弃类

    满足三个条件:
    1.在堆中没有任何关于该类的实例
    2,加载该类的ClassLoader被回收
    3.该类对应的Class对象没有被任何地方引用,无法通过反射的方法来访问该类

    六.类加载的过程

    1.加载

    1.通过类全限定名来获取二进制字节流
    2.将Class常量池的静态储存结构转为运行时数据结构
    3.生成一个Class类对象在方法区中用来外界访问的入口

    2.连接

    2.1验证

    检查二进制字节流是否符合jvm的Class文件规范

    2.2准备

    1.为静态变量分配内存
    2.字符串常量池、静态变量在jdk7之前在永久代中但在jdk7之后放在堆中了
    3.附最初始值(不是真正的初始值)

    2.3解析

    将常量池中符号引用换成直接引用

    2.4初始化

    初始化阶段是执行初始化方法 ()方法的过程

    2.5卸载

    满足被卸载的三个条件
    与上面三个相同

  • 相关阅读:
    Linux环境下Docker安装mysql数据的导入和导出备份操作
    使用Scrapy的调试工具和日志系统定位并解决爬虫问题
    激光测距仪非接触式地表裂缝监测仪
    【WPF应用31】WPF基本控件-ListView的详解与示例
    【Vue基础二】--- 计算属性,watch,style,class,条件列表渲染
    关于RXJS,何时需要取消订阅 / 可观测对象的冷热之分
    跑步需要哪些运动装备?跑步装备选购指南
    leetcode:914. 卡牌分组(python3解法)
    设计超萌的机械键盘,超有手感还不吵人,雷柏MT510PRO键盘上手
    Kafka学习笔记
  • 原文地址:https://blog.csdn.net/weixin_48102099/article/details/126081882