精神小伙儿探秘JVM(一)

JVM是什么?

JAVA Virtual Machine,即Java虚拟机。传说Java是可以跨平台的,为啥这么牛?这就要归功于JVM的功劳。Java程序只需要被编译成字节码,即.class文件,通过字节码就可以在虚拟机上运行。这样,不管你的操作系统是啥,只要安装了JVM,Java程序都能畅通无阻的运行了。

JVM的内存结构是啥样的?

JVM作为一个虚拟机,指定得有自己的内存结构。那这内存结构是啥样的呢?

我们计算机内存结构,分为堆区、栈区、代码区、数据区,那JVM是不也这样式儿的呢?还是有啥别的幺蛾子?上图,少废话。

其实是这样式儿的。主要分为这几个区域:程序计数器,Java虚拟机栈,本地方法栈,堆,方法区。其实作用和实际的堆栈有点类似。下面就一一说说这几部分都是干哈的。

程序计数器

这家伙是一小段内存,存当前线程正在执行的那条字节码指令的地址。说白了就是存执行哪条指令的地址用的。若当前线程正在执行的是一个本地方法,那么此时程序计数器为Undefined。敢问啥是本地方法?解答:Java内部提供的方法,叫Java方法;如果是其他库或者操作系统提供的方法,就叫本地方法。比如你调用一个操作系统中的写文件或者获取文件大小等。但这就带来个问题,不同的操作系统,对外的接口方法可是不一样的呀,这还咋跨平台了。Java大神们想到了这点,封了个JNI接口,通过它来调用本地方法接口就爽歪歪了。有点扯远了,回归主题。

Java虚拟机栈

简称Java栈。这玩意就是个栈,功能也一样,只不过是用来描述 Java 方法运行过程的内存模型的。虚拟机栈会为每一个即将运行的 Java 方法创建一块叫做“栈帧”的区域,用于存放该方法运行过程中的一些信息。包括局部变量表,操作数栈,动态链接,方法出口信息等等。挺多,其实就是调用一个方法需要的所有信息,都放在里头。

运行方法的时候,发现有个局部变量,好,把它的值扔进局部变量表里头。和栈一样,栈顶存的是当前正在执行的方法,PC寄存器指向它,从它里面获取本地变量作为操作数,当然也只能从它里面获取。如果这里头又调用了新的方法,后来者居上,新的顶替旧的,新栈帧占领高地!

如果方法执行完了,栈帧就被移除,这个操作会产生一个返回值,这个返回值就是新的活动栈帧中的一个操作数。如果没有返回值,表明栈帧没有被移除,对应的操作数也不会发生变化。

所以,虚拟机栈,每个线程都会有一个自己独立的栈控件,彼此井水不犯河水,不用担心什么数据不一致,线程锁的问题了。这说明什么?有个属于自己的女朋友,很重要。。。

但这个虚拟机栈也会有侧漏的情况?主要有StackOverFlowError 和 OutOfMemoryError。这都啥玩意?先说StackOverFlowError ,虚拟机栈的大小不允许动态扩展,线程还请求了好大的空间,不好意思,没地方了,滚!这就是StackOverFlowError ;那OutOfMemoryError是啥?栈允许动态扩展,但扩展总有到头的一天,大胃王也有撑死的时候,吃不下了你还喂,这就叫OutOfMemoryError。所以如果有一天,出现了StackOverFlowError,旁边一死胖子大喊:“ca,没空间了!”,你只需微微一笑,告诉它啥叫StackOverFlowError ,啥叫还有空间,啥叫小朋友你是否有很多问号。

本地方法栈

这玩意又叫C栈,为本地方法准备的,本地方法语言主要是C写的,所以有了这外号。这个工作原理和Java栈一样,就是地位不高,如果不用,直接给整没了就行。

堆干啥的?答:放对象的。没错,对象都搁这里头呢。在这里头,是个无产阶级大家庭,彼此不分你我,资源共享。虚拟机已启动,这个大家庭就诞生了。由于这里不执行计划生育,导致生育质量不高,产生不少有害垃圾。。。好吧,这个解释有点夸张,总之所谓的垃圾回收主要就这对这片区域。

堆大小和栈一样,可以固定也可以扩展。主流的虚拟机都是可以扩展的,但也会有资源枯竭的一天。这就是所谓的内存泄露OutOfMemoryError。

方法区

这片区域,其实是堆的一个逻辑部分。所以堆区域有什么政策,它也有。主要存放已经被虚拟机加载的类信息,常量,静态变量,即时编译器编译后的代码。我们称它为方法区,堆区对它有自己的称呼“永久代”---这主要是由于它里头存的要么是常量,都是长期用到的。也因为这样,虽然它在堆里头,但不是垃圾回收的重点,主要回收目标是:对常量池的回收;对类型的卸载。有了这么宽松的政策,它甚至可以不回收垃圾,也没人管。

当类被Java虚拟机加载后,.class文件中的常量就存在方法区的运行时常量池里头。而且在运行期间,可以向常量池中添加新的常量。如 String 类的 intern() 方法就能在运行期间向常量池中添加字符串常量。这个intern()是干啥的?上代码:

 String s3 = new String("123") + new String("123");
        s3.intern();
        String s4 = "123123";
        System.out.println(s3 == s4);

在jdk6中,输出false,因为intern方法将字符串复制到常量区,然后返回一个该字符串在常量区中的引用。但是s3并没有接收这个引用,因此s3指向的还是堆,但是s4指向的是常量区,因此这两个地址不一样。

在jdk7 中,输出true,因为jdk7中intern方法是(在常量区找不到该字符串时)将该字符串对象在堆里的引用注册到常量区,以后使用相同字面量(双引号形式)声明的字符串对象都指向该地址,也就是该字符串在堆中的地址。所以,调用s3的intern方法后返回的引用就是s3本身的引用,而使用字面量声明的s4也是指向这个引用的,所以这两个地址相同。

最后一个,直接内存。

也叫堆外内存。就是Java虚拟机之外的内存,这片内存也能被Java用。怎么用呢?NIO 中引入了一种基于通道和缓冲的 IO 方式。它可以通过调用本地方法直接分配 Java 虚拟机之外的内存,然后通过一个存储在堆中的DirectByteBuffer对象直接操作该内存,而无须先将外部内存中的数据复制到堆中再进行操作,从而提高了数据操作的效率。(NIO是Java 4里面提供的新的API,目的是用来解决传统IO的问题。)

直接内存和堆内存有什么区别?

直接内存申请空间耗费更高的性能;直接内存读取 IO 的性能要优于普通的堆内存。直接内存作用链: 本地 IO -> 直接内存 -> 本地 IO;堆内存作用链:本地 IO -> 直接内存 -> 非直接内存 -> 直接内存 -> 本地 IO。

C++,小Java,精神小伙最潇洒。你也赞,我也夸,傻帅的我最奇葩。下回说HotSpot。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

林小BA

请作者增肥

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值