转行学java之前,总是听着大佬们说着java像个渣男一样可以跨平台,一次编译到处运行,瞬间,我就坚定了学java的信念,哎呀妈呀,得劲。真的学java之后,好像渣男也不是那么好学的,尤其这货的必杀技,各年龄段(操作系统)通杀太难了,这可激起了我的小暴脾气,还有能难倒小灏哥的,终于在我兢兢业业的努力下,发现了一个道理,还真有。随着慢慢攻克,看书,看看视频课,也了解了些皮毛,写出来当做个笔记吧。
学习的第一步,就是去oracle官网下载,下载完打开发现有俩目录jdk和jre,这就尴尬了,我要学的不是jvm虚拟机吗,我要学的不是他那处处可留情的必杀技吗,咋越整越多了,这三个东西困扰了我很久,背过,半个月就忘,这可咋整,我可是要当海贼王的男人,怎么能被区区jvm,jdk,jre难倒呢,耳边响起了某位大佬的二字真言:理解。背是行不通了,再花点时间钻研钻研吧。先百度瞅瞅:
jvm:Java Virtual Machine(java虚拟机)
jre:Java Runtime Environment(java运行环境)
jdk:java development kit (java开发工具包)
还是没太搞懂是啥,先看一下java下载下来的jdk和jre有啥吧,先打开jdk,
里面有一些目录和文件,其中一个好像很熟悉的样子-----jre,咦,这个不是在外层目录吗,难道他会瞬移,退回上级目录:
还好,他还在,点开两个jre:
基本上是一样的,我就猜这jdk是不是就是包含jre的呢,不然这oracle官网下载下来的东西,还能有问题?
这时候,大牛们就该读源码了,而我则继续百度,发现了一张图:
这张图应该是比较全面了,我们通常说的jdk是包含jre的,而通常说的jre是包含jvm的,jvm运行在os操作系统之上,而像一些开发工具,则是在这jdk基础之上,做了更多的完善,让我们更方便的去开发代码。
了解了他们的关系之后,再逐一了解他们的功能:
大家应该都知道,jvm是个渣男,他可不只是java的jvm,很多语言都可以在jvm上运行,只要你可以编译成class文件,我都可以任你在我的肉体上运行,它的最大功能也就是解释执行class文件。
jre比起jvm来说,稍微收敛一点,他包含了运行class文件需要的类库,他虽然也渣,但是好歹是知道老婆还是java,只不过背地里在什么系统上鬼混就说不准了。
jdk对于java开发来说,最直接面对的,也是最熟悉的,jdk包含了jre,是我们开发用的,其实没有jdk也能运行程序了,但是作为一个开发,没有jdk,你写的代码不能编译成class也没用。
我们现在也算是知道这三个渣男有啥关系,分别有啥用,对于学习java虚拟机来说,也算是打过招呼了,那么接下来,就先深入了解一下jvm的内存是怎么划分的:
线程私有区域:本地方法栈,虚拟机栈,程序计数器
线程共享区域:方法区,堆,运行时常量池
直观点的可以看下上面的内存区域图,细心点的会发现,这个图比我上面写的多了一个叫直接内存的灰色地带,他可是不被外界承认的备胎,他不属于运行时数据区,但是却又经常会用到他,备胎功底雄厚,后面介绍完了这些正牌,也介绍一下他。
虚拟机栈:
首先来聊一下虚拟机栈,大家都知道,栈的数据结构是先进后出,每个线程都会有自己的一块虚拟机栈,虚拟机栈里面是一个个的栈帧,在每次方法调用时,就会创建一个栈帧并将对应的栈帧压栈,方法执行完出栈,就这样跟着方法调用的节奏频繁的一进一出,在设计的时候,也是考虑了方法调用的模型。
栈帧里面主要包含局部变量表,操作数栈,动态连接和返回地址:
局部变量表: 用来存放八大基本类型和局部变量对象引用的地方,我们在方法执行的时候,传参、定义的局部变量都会存放到这里,供计算时,操作数栈存取,它是一个32位的长度,一般的32位也可以放的下,放不下的,占64位的高低位存放。
操作数栈:在执行计算操作时,会从局部变量表里面获取数据(可以使任意java数据类型)放到操作数栈,然后计算完成再从操作数栈移除,并存入局部变量表。
动态连接:这货有点牛,java从入门到放弃都知道这个-->多态,后面聊动态分派时会详细聊到。
返回地址:正常调用程序计数器的返回地址作为返回,具体操作:
1.恢复局部变量表和操作数栈
2.返回值压入调用者操作数栈
3.调整程序计数器指向下一个命令处
其实这一块理解不够透彻的可以想象一下方法的调用,比如我在一个方法里面调用另一个方法,我在方法调用完成出来之前首先要将这个栈帧的数据清掉,返回值自然是外层方法要用到的了,然后呢,就要调用下一步了,自然要调整程序计数器的指向。
异常返回通过异常处理器表来确定。
程序计数器:
上面说了,程序计数器是线程私有的,他是记录了当前线程执行的字节码行号地址,由于 Java 是多线程语言,当执行的线程数量超过 CPU 核数时,线程之间会根据时间片轮询争夺 CPU 资源。如果一个线程的时间片用完了,或者是其它原因导致这个线程的 CPU 资源被提前抢夺,那么这个退出的线程就需要单独的一个程序计数器,来记录下一条运行的指令,如果没有程序计数器的保证,线程无法保证当前程序顺序执行。
相比于普通方法,native方法是特殊的,他的执行是不是jvm具体管理的,所以jvm的程序计数器在执行native方法的时候,一直保持为空,它的调用是操作系统层面的程序计数器来记录的。
本地方法栈:
跟虚拟机栈没啥区别,虚拟机栈执行普通方法,本地方法栈执行native方法
上面介绍完了线程私有的jvm内存区域划分,其实对于线程私有的对于天生多线程的java,是否也是一个天生线程安全的方式呢,由jvm自行处理,又不用加锁就可以实现线程安全,我觉得也是我们在设计线程安全需要考虑的方向吧。
接下来开始介绍线程共享的,首先来介绍方法区:
方法区:
方法区是线程共享的,他主要存储类的结构信息,运行时常量池(1.8以后放到堆里面了,但其实他的定位应该还是属于方法区的范畴),构造函数,方法数据等,总的来说其实就是存放初始化时需要用到的数据。
方法区和永久代以及元空间是很容易搞混的,尤其是永久代和方法区,其实方法区相当于是jvm对内存的逻辑划分,而永久代和元空间是jdk1.7以前和jdk1.81以后分别对方法区的不同实现,我们以前用过jdk1.6版本,公司很抠门,都是2G的内存,使用默认的配置经常会出现 java.lang.OutOfMemoryError: PermGen异常,相信这个异常很多人都很熟悉,其实这个就是永久代内存溢出,而现在,我们一般都用16g内存,而且jdk1.8之后的元空间不设置参数只受本机内存的限制,想要体验一下的同学可以在vm参数加上-XX:MaxMetaspaceSize=1M,就能很快看到OutOfMemoryError: Metaspace,可见两者实现还是不一样的。
看到这里,oracle官方为啥要将永久代弃掉而改用元空间呢,首先按官方来说,是为了融合HotSpot和JRockit,因为JRockit没有永久代,其次的话,永久代必须要配置分配空间,而太小很容易内存溢出,太大又太占空间,为其分配多少内存很难确定(永久代的大小依赖因素太多,如常量池大小,方法的大小,class等)永久代在每次FullGc都可能呗回收,而回收率很低,而元空间存储在本地内存,不需要考虑这么多。
运行时常量池:
运行时常量池就是用来将class常量池的符号引用转为直接引用。
堆:
堆是jvm上最大的一块区域,也是我们后面聊到的垃圾回收器GC主要光顾的地方,因为我们几乎所有的对象(几乎是因为会存在栈上分配以及八大基本对象等,但这些毕竟是小部分)都在堆中,堆给人的感觉就是个沙盘,承载了那么多的对象就像是一个个沙兵,而这些对象的引用就像是一根根连接沙兵的线都放在栈上(结合上面说的其实就是放到局部变量表里,这时候就有问题了,局部变量表是线程私有的,那我们常说的线程安全还需要考虑吗,其实就是因为这边存放的是引用,别的线程也拿到一个引用指向这个对象,那还不是随便改),栈就像是个军师一样,在沙盘上指点着干掉这个引用,垃圾回收器回收的时候就会把这些对象给干掉(当然不止这么简单,垃圾回收后面也会细讲)。
直接内存:
直接内存又叫堆外内存,jvm运行时会首先申请一块块内存,分配给堆、栈、方法区等,而jvm没有申请的内存,就是直接内存,其实就是jvm管不到的地方,很神奇哈,jvm管不到的地方,为啥要了解呢,因为随着java生态的发展,我们不仅仅要用好申请的资源,没申请的有机会也要用好,提升资源的利用率,比如说使用NIO的话,就会频繁用到这一块,可以用java的directByteBuffer 对象操作,也可以用-XX:MaxDirectMemorySize来设置它的大小。
这一篇简单介绍了一下jvm的轮廓,相信看完之后,对jvm大体上有一个全面的了解了吧,jvm是是比较基础的知识,枯燥无味,很多东西看不见,摸不着,平时可能还用不到,但学好java,还是必须要有这方面的基础的,希望看到我文章的有缘人一起学习,共同进步吧。
.tb_button { padding: 1px; cursor: pointer; border-right: 1px solid rgba(139, 139, 139, 1); border-left: 1px solid rgba(255, 255, 255, 1); border-bottom: 1px solid rgba(255, 255, 255, 1) }
.tb_button.hover { borer: 2px outset #def; background-color: rgba(248, 248, 248, 1) !important }
.ws_toolbar { z-index: 100000 }
.ws_toolbar .ws_tb_btn { cursor: pointer; border: 1px solid rgba(85, 85, 85, 1); padding: 3px }
.tb_highlight { background-color: rgba(255, 255, 0, 1) }
.tb_hide { visibility: hidden }
.ws_toolbar img { padding: 2px; margin: 0 }