jvm 的结构 以及java类加载实例

JVM内存结构

在这里插入图片描述
这里通过实例,来讨论一个类由java源码 到运行到jvm中,是如何运作的。
在这里插入图片描述
java源码 被加载到jvm当中的流程
1、首先要把java Source 编译成 java Class字节码
2、在运行java Class字节码时,会生成一个虚拟机,这个虚拟机会开启一个main 主线程,主线程是用来执行main主方法的。
3、main 主方法 是通过在虚拟机栈分配的空间开始运行。
4、但是在main主线程运行前,它发现main 所在的Main 类自己在jvm找不到该信息。
5、于是要对Main 这个类做加载,它会把Main类信息加载到方法区当中,在执行main方法过程中,发现的Student类 也会加载把Student类信息加载到方法区当中,同时会把Student 实例加载到堆内存。
6、其中的 stu 对象的标识 包括形参 args 都会放到 虚拟机栈中
7、调用的study()方法 是在虚拟机栈中运行
8、hashCode()方法 是到本地方法栈中运行。
9、当出现stu =null,那么意味着这个实例 是没有被引用的,即可以用作垃圾回收处理的对象。
10、解释器的目的是,在计算机底层运行代码时,需要用解释器把代码解释为计算机语言,如二进制内容。
11、即时编译器的目的是,有些经常用到的代码,不能说用一次就被解释器解释一次,而是用即时编译器把这些常用的代码进行解释并且放到缓存当中,需要时就从缓存当中获取。

JVM内部结构的混淆点

(1)方法区、永久代、元空间

  • 方法区是 JVM 规范中定义的一块内存区域,用来存储类元数据、方法字节码、即时编译器需要的信息等
  • 永久代是 Hotspot 虚拟机对 JVM 规范的实现(1.8 之前)
  • 元空间是 Hotspot 虚拟机对 JVM 规范的另一种实现(1.8 以后),使用本地内存作为这些信息的存储空间

永久代和元空间都是对方法区的一种实现。
对于Java8, HotSpots取消了永久代,那么是不是也就没有方法区了呢?当然不是,方法区是一个规范,规范没变,它就一直在。那么取代永久代的就是元空间。它可永久代有什么不同的?存储位置不同,永久代物理是是堆的一部分,和新生代,老年代地址是连续的,而元空间属于本地内存;存储内容不同,元空间存储类的元信息,静态变量和常量池等并入堆中。相当于永久代的数据被瓜分到了堆和元空间中。

(2)Class文件常量池
Class 文件常量池指的是编译生成的 class 字节码文件,其结构中有一项是常量池(Constant Pool Table),用于存放编译期生成的各种字面量和符号引用,这部分内容将在类加载后进入方法区的运行时常量池中存放。

这里的字面量是指字符串字面量和声明为 final 的(基本数据类型)常量值,这些字符串字面量除了类中所有双引号括起来的字符串(包括方法体内的),还包括所有用到的类名、方法的名字和这些类与方法的字符串描述、字段(成员变量)的名称和描述符;声明为final的常量值指的是成员变量,不包含本地变量,本地变量是属于方法的。这些都在常量池的 UTF-8 表中(逻辑上的划分);
符号引用,就是指指向 UTF-8 表中向这些字面量的引用,包括类和接口的全限定名(包括包路径的完整名)、字段的名称和描述符、方法的名称和描述符。只不过是以一组符号来描述所引用的目标,和内存并无关,所以称为符号引用,直接指向内存中某一地址的引用称为直接引用;

(3)运行时常量池
运行时常量池是方法区的一部分,是一块内存区域。Class 文件常量池将在类加载后进入方法区的运行时常量池中存放。一个类加载到 JVM 中后对应一个运行时常量池,运行时常量池相对于 Class 文件常量池来说具备动态性,Class 文件常量只是一个静态存储结构,里面的引用都是符号引用。而运行时常量池可以在运行期间将符号引用解析为直接引用。可以说运行时常量池就是用来索引和查找字段和方法名称和描述符的。给定任意一个方法或字段的索引,通过这个索引最终可得到该方法或字段所属的类型信息和名称及描述符信息,这涉及到方法的调用和字段获取。

(4)字符串常量池
字符串常量池是全局的,JVM 中独此一份,因此也称为全局字符串常量池。运行时常量池中的字符串字面量若是成员的,则在类的加载初始化阶段就使用到了字符串常量池;若是本地的,则在使用到的时候(执行此代码时)才会使用到字符串常量池。其实,“使用常量池”对应的字节码是一个 ldc 指令,在给 String 类型的引用赋值的时候会先执行这个指令,看常量池中是否存在这个字符串对象的引用,若有就直接返回这个引用,若没有,就在堆里创建这个字符串对象并在字符串常量池中记录下这个引用(jdk1.7)。String 类的 intern() 方法还可在运行期间把字符串放到字符串常量池中。JVM 中除了字符串常量池,8种基本数据类型中除了两种浮点类型剩余的6种基本数据类型的包装类,都使用了缓冲池技术(即存储到字符串常量池中),但是 Byte、Short、Integer、Long、Character 这5种整型的包装类也只是在对应值在 [-128,127] 时才会使用缓冲池,超出此范围仍然会去创建新的对象。其中:

在 jdk1.6(含)之前,永久代(方法区的实现)和堆相互隔离。所以字符串常量池是方法区的一部分,并且其中存放的是字符串的实例;
在 jdk1.7(含)之后,永久代(方法区的实现)中的两部分(字符串常量池和static静态变量)挪到堆内存之中。永久代中的其它部分(如类的元信息(属性名、方法名、参数名等)还在永久代中。且字符串常量池中存储的value只是字符串对象的引用,真正的字符串实例对象是存储在堆中;
jdk1.8 已移除永久代,替换为元空间,同时1.7中存放于永久代中的类的元信息也挪过来。那两部分(字符串常量池和static静态变量)还是和1.7一样存储在堆内存中,存储的value也仍然只是引用。

(5)类加载完成后在内存中储存
类加载完成后主要包括类信息以及类Class对象,其中类信息保存在方法区中,类Class对象保存在堆区。

类信息主要包含运行时常量池、类型信息、字段信息、方法信息、类加载器的引用、对应class实例的引用等信息。

类加载器的引用:这个类到类加载器实例的引用

对应class实例的引用:类加载器在加载类信息放到方法区中后,会创建一个对应的Class 类型的对象实例放到堆(Heap)中, 作为开发人员访问方法区中类定义的入口和切入点。

JVM 内存参数

  • -Xms 最小堆内存(包括新生代和老年代)
  • -Xmx 最大堆内存(包括新生代和老年代)
  • 通常建议将 -Xms 与 -Xmx 设置为大小相等,即不需要保留内存,不需要从小到大增长,这样性能较好
  • -XX:NewSize 与 -XX:MaxNewSize 设置新生代的最小与最大值,但一般不建议设置,由 JVM 自己控制
  • -Xmn 设置新生代大小,相当于同时设置了 -XX:NewSize 与 -XX:MaxNewSize 并且取值相等
  • -XX:NewRatio=2:1 表示老年代占两份,新生代占一份
  • -XX:SurvivorRatio=4:1 表示新生代分成六份,伊甸园占四份,from 和 to 各占一份

类加载过程到jvm实例

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
1、在上述main方法中,当要执行 在这里插入图片描述
这行代码时,因为类是懒加载的缘故,所以这是才会把Student 加载到虚拟机中,此时该类是被加载到方法区当中,而Student.class 实例 是被加载到堆内存当中。加载之后的效果如下图:
在这里插入图片描述

可见在类加载时,只有static final 对象被赋值,而静态对象 只是给默认值没有被赋值,
而成员变量是跟着实例对象在堆内存的。

当根据student 实例化了一个对象之后,也就是完成了student的初始化之后,a,b两个静态变量被赋值。
在这里插入图片描述
同时在初始化之后,实例的成员变量也成功被加载和赋值。
在这里插入图片描述

小结:这里有点混乱需要根据下图做一个小结:
图1
图一
图2
在这里插入图片描述
图3
在这里插入图片描述

图1
1、类的加载是懒加载,只有用到了该类才会去加载。
2、当加载类时类中被static 修饰的变量 只会做声明,不会被赋值,而static final 修饰的非引用类型
变量会被声明和赋值。
3、当类被实例化成为一个对象时,就是发生了对象的初始化,此时类中被static 修饰的和被static final
修饰的引用类型变量才会被赋值。
图2
1、在使用类中的被final static 修饰的非引用类型变量时,不会引发类的加载
2、在使用类中的被final static 修饰的引用类型变量时,会引发类的加载和初始化
图3
1、在加载图3 TestResolution时,在执行到在这里插入图片描述
TestResolution的常量池 只有A、B、C三个符号引用,而当实例化对象之后,三个符号引用就变成了直接引用。
在这里插入图片描述

双亲委派机制

在这里插入图片描述

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

在这里插入图片描述
双亲委派机制

所谓的双亲委派,就是指优先委派上级类加载器进行加载,如果上级类加载器

  • 能找到这个类,由上级加载,加载后该类也对下级加载器可见
  • 找不到这个类,则下级类加载器才有资格执行加载

双亲委派的目的有两点

  1. 让上级类加载器中的类对下级共享(反之不行),即能让你的类能依赖到 jdk 提供的核心类。(避免 起名与核心类重名,从而覆盖核心类的功能,因为在双亲委派机制下,重名也只会加载在该加载器下的包的类)

  2. 让类的加载有优先次序,保证核心类优先加载。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值