浅谈JVM,走进JVM的世界

走进JVM的世界,重新认识JVM  ----->>>>>>>>原来JVM的世界我也懂

      JVM有2种运行模式,一个是server,一个是client,client启动较快,而server模式启动启动较慢,但能长期稳定运行,运行之后会比client的速度快,我们常用的也是server模式,如何查看使用的是什么模式  很简单  java -version 就清楚了。

目录

1.什么是JVM

2.什么是class文件,被编译后的class字节码你还认识吗?

3.类加载(JVM的类加载是通过ClassLoader及其子类来完成的)

4.类加载过程(不同来源的.class字节加载到jvm内存中【方法区】)

5.运行时数据区

A.方法区

B.java堆

C.方法区和元空间区别


1.什么是JVM

JVM是一个跨平台虚拟机,JVM和class有扯不清的关系,直接上图,让你重新认识jvm,原来jvm是这个样子

浅谈JVM,走进JVM的世界

 

2.什么是class文件,被编译后的class字节码你还认识吗?

      被编译后的class字节码文件内容为16进制,都是以 CA FE BA  BE 开头 [ 俗称魔数(magic)] +副版本号 +主版本号+常量池计数器+常量池数据区+访问标志+类索引 +父类索引 +接口计数器 +接口信息数据区+字段计数器+方法信息数据区 +方法计数器 +方法信息数据区 +属性计数器 +属性信息数据区

浅谈JVM,走进JVM的世界

其中jdk1.8的16进制版本号为 00 00 00 34  jdk1.7的16进制版本号为 00 00 00 33

      常量计算器默认是从1开始,constant_pool_count = 1时,常量池中的cp_info个数为0;当constant_pool_countn时,常量池 中的cp_info个数为n-1

      常量池分为字面量和符号引用。
字面量:文本字符串、被声明的final常量值、基本数据类型、等。
符号引用:类和结构完全限定名、字段的名称和描述、方法名称和描述
...............
 

3.类加载(JVM的类加载是通过ClassLoader及其子类来完成的

  1.   类加载时机
  •    遇到 new getstatic putstatic invokestatic 这四条字节码指令时,如果对应的类没有
    初始化,则要对对应的类先进行初始化。
  • 使用 java.lang.reflect 包方法时对类进行反射调用的时候
  • 初始化一个类的时候发现其父类还没初始化,要先初始化其父类
  • 当虚拟机开始启动时,用户需要指定一个主类,虚拟机会先执行这个主类的初始化。

4.类加载过程(不同来源的.class字节加载到jvm内存中【方法区】)

         验证--->加载--->准备【不包含final修饰的static】--->解析【符号引用替换为直接引用的过程】--->初始化【同一个类加载器下,一个类型只初始化一次】
 
 
 

5.运行时数据区

JDK1.8前和JDK1.8后(包括1.8)内存模型是不一样的,1.8后方法区已迭代更名为元空间
 
浅谈JVM,走进JVM的世界
 

运行时数据区的使用顺序:方法区(类信息、常量池信息等)==> 堆(对象或者数组)==>虚拟机栈、程序计数器、本地方法栈(不 是必须的)

A.方法区

1、类型信息

  • 类型的全限定名
  • 超类的全限定名
  • 直接超接口的全限定名
  • 类型标志(该类是类类型还是接口类型)
  • 类的访问描述符(publicprivatedefaultabstractfifinalstatic

2、类型的常量池

存放该类型所用到的常量的有序集合,包括直接常量(如字符串、整数、浮点数的常量)和对其他类

型、字段、方法的符号引用。常量池中每一个保存的常量都有一个索引,就像数组中的字段一样。因为
常量池中保存着所有类型使用

3、字段信息(该类声明的所有字段)

  • 字段修饰符(publicprotectprivatedefault
  • 字段的类型
  • 字段名称

4、方法信息

  • 方法信息中包含类的所有方法,每个方法包含以下信息:
  • 方法修饰符
  • 方法返回类型
  • 方法名
  • 方法参数个数、类型、顺序等
  • 方法字节码
  • 操作数栈和该方法在栈帧中的局部变量区大小
  • 异常表

5、类变量(静态变量)

  • 指该类所有对象共享的变量,即使没有任何实例对象时,也可以访问的类变量。它们与类进行绑定。

6、指向类加载器的引用

  • 每一个被JVM加载的类型,都保存这个类加载器的引用,类加载器动态链接时会用到。

7、指向Class实例的引用

  • 类加载的过程中,虚拟机会创建该类型的Class实例,方法区中必须保存对该对象的引用。通过 Class.forName(String className)来查找获得该实例的引用,然后创建该类的对象。
8、方法表
  • 为了提高访问效率,JVM可能会对每个装载的非抽象类,都创建一个数组,数组的每个元素是实例可能 调用的方法的直接引用,包括父类中继承过来的方法。这个表在抽象类或者接口中是没有的。
9、运行时常量池(Runtime Constant Pool)
  • Class文件中除了有类的版本、字段、方法、接口等描述信息外,还有一项信息是常量池,用于存放编译器生成的各种字面常量和符号引用,这部分内容被类加载后进入方法区的运行时常量池中存放。
  • 运行时常量池相对于Class文件常量池的另外一个特征具有动态性,可以在运行期间将新的常量放入池 中(典型的如String类的intern()方法)。
 

B.java堆

堆被划分为老年代和年轻代。 年轻代和老年代的内存占比默认为12。年轻代又划分为:伊甸园(Eden)、两个幸存区(Survivor),伊甸园和两个幸存区的内存占比默认为81:1。之所以我们堆内存要进行以上这么细粒度的内存划分,是为了垃圾回收

垃圾回收针对不同情况的对象,回收策略(回收算法)是不同的的。而通过内存的划分,可以将不同的算法在不同的区域中进行使用。比如说年轻代使用了复制算法。 年轻代中的对象,生命周期很短,基本上是很快就死了,也就是被GC了。 老年代中的对象,都是一些老顽固,都是多次回收的对象或者大对象才存到老年代中

  • 内存分配原则
  1. 优先在 Eden 分配,如果 Eden 空间不足虚拟机则会进行一次 MinorGC
  2. 【大对象】直接接入老年代,【大对象】一般指的是【很长的字符串或数组】
  3. 【长期存活的对象】也会进入老年代,每个对象都有一个age

C.方法区和元空间区别

存储位置不同,永久代物理是是堆的一部分,和新生代,老年代地址是连续的,而元空间属于本地内存; 由于永久代它的大小是比较小的,而元空间的大小是决定于内地内存的。 所以说永久代使用不当,比较容易出现OOM异常。而元空间一般不会。存储内容不同,元空间存储类的元信息,[静态变量][常量池]等并入堆中。相当于永久代的数据被分到了堆和元空间中。


  • 勿喷,如有不正确的地方指出来,一起学习研究