浅谈JVM,走进JVM的世界
走进JVM的世界,重新认识JVM ----->>>>>>>>原来JVM的世界我也懂
JVM有2种运行模式,一个是server,一个是client,client启动较快,而server模式启动启动较慢,但能长期稳定运行,运行之后会比client的速度快,我们常用的也是server模式,如何查看使用的是什么模式 很简单 java -version 就清楚了。
目录
2.什么是class文件,被编译后的class字节码你还认识吗?
3.类加载(JVM的类加载是通过ClassLoader及其子类来完成的)
4.类加载过程(不同来源的.class字节加载到jvm内存中【方法区】)
1.什么是JVM
JVM是一个跨平台虚拟机,JVM和class有扯不清的关系,直接上图,让你重新认识jvm,原来jvm是这个样子
2.什么是class文件,被编译后的class字节码你还认识吗?
被编译后的class字节码文件内容为16进制,都是以 CA FE BA BE 开头 [ 俗称魔数(magic)] +副版本号 +主版本号+常量池计数器+常量池数据区+访问标志+类索引 +父类索引 +接口计数器 +接口信息数据区+字段计数器+方法信息数据区 +方法计数器 +方法信息数据区 +属性计数器 +属性信息数据区
其中jdk1.8的16进制版本号为 00 00 00 34 jdk1.7的16进制版本号为 00 00 00 33
常量计算器默认是从1开始,当constant_pool_count = 1时,常量池中的cp_info个数为0;当constant_pool_count为n时,常量池 中的cp_info个数为n-1。
3.类加载(JVM的类加载是通过ClassLoader及其子类来完成的)
- 类加载时机
- 遇到 new 、 getstatic 、 putstatic 和 invokestatic 这四条字节码指令时,如果对应的类没有
初始化,则要对对应的类先进行初始化。
-
使用 java.lang.reflect 包方法时对类进行反射调用的时候
-
初始化一个类的时候发现其父类还没初始化,要先初始化其父类
-
当虚拟机开始启动时,用户需要指定一个主类,虚拟机会先执行这个主类的初始化。
4.类加载过程(不同来源的.class字节加载到jvm内存中【方法区】)
5.运行时数据区
运行时数据区的使用顺序:方法区(类信息、常量池信息等)==> 堆(对象或者数组)==>虚拟机栈、程序计数器、本地方法栈(不 是必须的)
A.方法区
1、类型信息
- 类型的全限定名
- 超类的全限定名
- 直接超接口的全限定名
- 类型标志(该类是类类型还是接口类型)
- 类的访问描述符(public、private、default、abstract、fifinal、static)
2、类型的常量池
存放该类型所用到的常量的有序集合,包括直接常量(如字符串、整数、浮点数的常量)和对其他类
3、字段信息(该类声明的所有字段)
- 字段修饰符(public、protect、private、default)
- 字段的类型
- 字段名称
4、方法信息
- 方法信息中包含类的所有方法,每个方法包含以下信息:
- 方法修饰符
- 方法返回类型
- 方法名
- 方法参数个数、类型、顺序等
- 方法字节码
- 操作数栈和该方法在栈帧中的局部变量区大小
- 异常表
5、类变量(静态变量)
- 指该类所有对象共享的变量,即使没有任何实例对象时,也可以访问的类变量。它们与类进行绑定。
6、指向类加载器的引用
- 每一个被JVM加载的类型,都保存这个类加载器的引用,类加载器动态链接时会用到。
7、指向Class实例的引用
- 类加载的过程中,虚拟机会创建该类型的Class实例,方法区中必须保存对该对象的引用。通过 Class.forName(String className)来查找获得该实例的引用,然后创建该类的对象。
- 为了提高访问效率,JVM可能会对每个装载的非抽象类,都创建一个数组,数组的每个元素是实例可能 调用的方法的直接引用,包括父类中继承过来的方法。这个表在抽象类或者接口中是没有的。
- Class文件中除了有类的版本、字段、方法、接口等描述信息外,还有一项信息是常量池,用于存放编译器生成的各种字面常量和符号引用,这部分内容被类加载后进入方法区的运行时常量池中存放。
- 运行时常量池相对于Class文件常量池的另外一个特征具有动态性,可以在运行期间将新的常量放入池 中(典型的如String类的intern()方法)。
B.java堆
堆被划分为老年代和年轻代。 年轻代和老年代的内存占比默认为1:2。年轻代又划分为:伊甸园(Eden)、两个幸存区(Survivor),伊甸园和两个幸存区的内存占比默认为8:1:1。之所以我们堆内存要进行以上这么细粒度的内存划分,是为了垃圾回收
垃圾回收针对不同情况的对象,回收策略(回收算法)是不同的的。而通过内存的划分,可以将不同的算法在不同的区域中进行使用。比如说年轻代使用了复制算法。 年轻代中的对象,生命周期很短,基本上是很快就死了,也就是被GC了。 老年代中的对象,都是一些老顽固,都是多次回收的对象或者大对象才存到老年代中
- 内存分配原则
- 优先在 Eden 分配,如果 Eden 空间不足虚拟机则会进行一次 MinorGC
- 【大对象】直接接入老年代,【大对象】一般指的是【很长的字符串或数组】
- 【长期存活的对象】也会进入老年代,每个对象都有一个age
C.方法区和元空间区别
存储位置不同,永久代物理是是堆的一部分,和新生代,老年代地址是连续的,而元空间属于本地内存; 由于永久代它的大小是比较小的,而元空间的大小是决定于内地内存的。 所以说永久代使用不当,比较容易出现OOM异常。而元空间一般不会。存储内容不同,元空间存储类的元信息,[静态变量]和[常量池]等并入堆中。相当于永久代的数据被分到了堆和元空间中。
- 勿喷,如有不正确的地方指出来,一起学习研究