JVM小结

1. 图形示例

图片说明
我用visio画的,有什么不对的还请各位大佬海涵指正。

概述
Java的执行过程,应该是

  • Java源文件 -> 编译器 -> 字节码文件
  • 字节码文件 -> JVM -> 机器码

每一个平台,Win或Linux或Mac下,对应的解释器都是不同的,但虚拟机的实现都是相同的,我们用的最广泛的OpenJDK以及OracleJDK下的JVM都是HotSpot VM,以此为例说说我自己的认知8。
简单来划分,JVM虚拟机包含了:

  • JVM栈
  • Native本地方法栈
  • 方法区(1.8之后更为元空间)
  • 程序计数器

其中本地方法栈我们顾名思义,就是Native系统级方法的栈区,它由底层的C/C++所编写,调用的是本地方法库,不需要我们Java程序员考虑,接下来分开描述我的认识。


  1. 这个想必大家都很熟悉了,是一个被所有线程共享的内存区域,主要存放的是对象的实例,因此这里也是垃圾收集器操作的地方,在现在的GC都采用分代回收算法的环境下,堆又可分为Eden(伊甸/Young年轻代)以及Survior(From以及To)两块儿,还有一个老年代,这一块儿涉及到GC分代回收的过程,放到后面详细说明。

  2. JVM栈
    栈是JVM中尤为重要的一块儿区域,属于线程私有的,可以理解为栈就是一个线程,因为方法是层层调用,有外而内,即先入后出,满足栈这种数据结构的特征,故JVM方法运行选择了栈这种数据结构,其中我们程序中运行的每个方法都用一个栈帧表示,栈帧中主要包含了局部变量表,操作数栈,动态链接,返回地址,如下图
    图片说明
    其中局部变量表,用来存储你所执行的每一个方法中的局部变量,比如你在test方法中有 int a = 1; int b = 2 ;执行步骤为:
    首先在局部变量表中就会有a,b两个变量,但注意,此时a和b两个变量并没有值,他们仅仅只是在内存空间中开辟了这么一块儿区域用来存放变量而已。
    然后就是操作数栈的活儿了,它也是个栈,它会把1,2从常量池中拿出来,计算或者赋值给a和b,平常的计算也会在这里完成,如果变量是对象,它就是引用指向堆qidah,和基本类型差不多,同时也兼顾参数传递的操作。
    动态链接指定方法的调用,这里有方法分派的问题,因为本人也不是很熟悉,不细说了,可以自己下去了解,有静态分派和动态分派。
    当方法执行完毕或者终止,无论是正常执行完毕,还是执行异常导致的终结,都需要找到上一层调用它的地址,举个例子,你去面试,从你住的地方区公司面试,无论是面试成功与否,你都要回来你住的地方,而这个返回地址,就是标记你回来的地址定位,就这个意思。

  3. 本地方法栈
    这个其实不用Java程序员考虑,它是系统级方法的栈区,内部结构类似于JVM栈,但还是不一样,不过这个区域不用你管就是了,略过。

  4. 方法区
    也称为永久代,是JDK1.8之前的叫法,JDK1.8之后,方法区改为了元空间,其实在这里方法区已经不属于JVM模型了,它现在直接存储在内存区域,但依然符合JVM规范,没错,注意JVM是一种规范或者统称,只要你开发的虚拟机符合JVM官方规定的规范,那么你的JVM也是一套独立的JVM,实现JVM规范的虚拟机有很多,例如自带的HotSpot,阿里的龙井等等。因为它依然符合规范,所以你将它划到JVM模型中也没啥问题。
    方法区用来存储常量,静态变量以及类元信息,所谓的类元信息,就是类,枚举,接口,注解。常量池就是在方法区中,但其实这是1.7之前的模型,在1.7之后,常量池其实已经被移到了堆中,举个栗子,字符串有个intern()方法,该方法回去常量池中寻找当前调用改方法的字符串常量并返回,如果找到了,它就直接该字符串对象,如果没找到,九八当前的字符串放到常量池里面再返回。
    例如

    String str1 = new String("来了老弟");
    String str2 = new String("来了老弟");
    System.out.println(str1.intern() == str2.intern());

    运行结果为true
    那么这样,执行如下代码

    String string1 = new String("来了") + new String("老弟");
    String string2 = "来了老弟";
    String string3 = new String("来了") + new String("老弟");
    System.out.println(string1 == string2);             //false
    System.out.println(string2.intern() == string2);    //true
    System.out.println(string3.intern() == string3);    //false
    System.out.println(string3.intern() == string2);    //true

    结果为图片说明

wdnmd是不是就很离谱?怎么第一次是true第二次就是false呢?耐心,来慢慢理解

首先string1和2一定是不相等的两个引用,没有问题,string2.intern()的时候,会把string2的值放进常量池中,这时候他们就指向的都是常量池中的一个“来了老弟”,所以是相等的。

然而string3怎么就不行了呢?这时候要对照着最后一行来看。结果是true,我们都知道,java为了节省性能,如果直接用“”来创建字符串,那么他会自动再常量中寻找相同的,这里也有这样的思维,当string3创建的时候,堆中有了新的“来了老弟”,然后这个引用指向了string3,这时候调用string3.intern(),他会寻找是否有“来了老弟”这样的内容,没有就放进去,有就直接返回,然后由于我们先调用了string2的intern()方法,因此直接返回地是string2的引用,而string3的内容还放在堆里,他们的引用当然是不同的。
最后string3.intern()等于string2,说明string3.intern()返回的是已经存进进常量池的string2的引用。

以上,可以证实,常量池其实是在堆中了已经。

程序计数器
这个没什么好说的,就是指向你代码执行哪一行就行了,如果你有兴趣,可以区用命令反编译一下class文件,你会发现每一行都有个数字,调用的时候就是说执行多少行这样子

全部评论

相关推荐

牛客410815733号:这是什么电影查看图片
点赞 评论 收藏
分享
11-08 16:53
门头沟学院 C++
投票
滑模小马达:第三个如果是qfqc感觉还行,我签的qfkj搞电机的,违约金也很高,但公司感觉还可以,听说之前开过一个试用转正的应届生,仅供参考。
点赞 评论 收藏
分享
不愿透露姓名的神秘牛友
11-26 18:54
说等下个版本吧的发呆爱好者很贪睡:佬最后去了哪家呀
点赞 评论 收藏
分享
评论
点赞
收藏
分享
牛客网
牛客企业服务