转行学java以前,老是听着大佬们说着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开发工具包)oracle
仍是没太搞懂是啥,先看一下java下载下来的jdk和jre有啥吧,先打开jdk,jvm
里面有一些目录和文件,其中一个好像很熟悉的样子-----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,仍是必需要有这方面的基础的,但愿看到我文章的有缘人一块儿学习,共同进步吧。