该文章是分享Java和JVM的知识,是我在学习过程中的笔记以及总结,整理记录的过程不能用单纯用脑袋记,定期设立一个目标,例如一周更新一篇,好记性不如烂笔头,经常复习才是王道,切忌三天打鱼两天晒网。
接下来开始今天的分享:
java简介
1、java的三个体系
java是面向对象程序语言java和java平台的总称,java有三个体系:
JAVASE(java2PlatformStandardEditor)java平台标准版
JAVAEE(java2PlatformEnterpriseEditor)java平台企业版
JAVAME(java2PlatformMicroEditor)java平台标准版
JavaSE是JavaEE和JavaME的基础。
JavaEE是在JavaSE的基础上构建,用以开发B/S加工软件,即开发企业级应用。JavaEE在JAVASE的基础上扩展,添加了许多便捷的框架,譬如我们常用的Spring,Struts,Hibernate。JavaSE也可以说既是框架也是规范,它包含了许多我们开发时用到的组件如Servlet,EJB,JSP,JSTL,同时JAVAEE提供了许多规范的接口却并不实现,使得即使不同厂商实现细节不同,展现给外部使用的却是统一规范的接口。
JavaME则是一套专门为嵌入式设备设计的api接口规范,专门用于开发移动设备软件和嵌入式设备软件。
注:之前有接触过androidstudio,关于android软件的开发和JavaME,我看到的说法是两者唯一的关系就是都是java语言实现的。android软件不能运行在javaME的环境中,javaME软件也不能运行在android环境中。
2、java的主要特性
java语言是简单的,因为它的语法与C和C++相似,但是又抛弃了一些C++中很少使用的、复杂的特性,比如操作符重载,多继承,自动的强制类型转换,并且舍弃了指针的使用,提供了自动分配和回收内存的垃圾处理器。
java语音是面向对象的,java语音提供了类、接口和继承等面向对象的特性,不过java的类不支持多继承,接口支持多继承,java提供了类于接口之间的实现机制,并且全面支持动态绑定。
Java语言是分布式的,它支持internet应用开发,它的网络应用编程接口(javanet)提供了支持网络应用开发的类库,譬如URL,URLconnection,Socket,ServerSocket等,其中java的RMI(远程方法激活)机制也是开发分布式应用的重要手段。
Java语言是健壮的,java的强类型机制、异常处理、垃圾的回收器是java程序健壮性的重要保证,java丢弃指针机制是明智的,java的安全检查机制使得java更具健壮性。
java语言是安全的,它提供了一个安全机制以防止来自网络的恶意代码的攻击。java对通过网络下载的类具有一个安全防范机制(类ClassLoader),如分配不同的名字空间防止替代本地同名类、字节代码检查,并提供安全管理机制(类SecurityManager)让java应用设置安全卫兵。
Java语言是体系结构中立的,java程序(.java)在java平台中被编译成体系结构中立的字节码格式(.class),然后可以在实现了java平台的任何系统中运行,这种途径适合于异构的网络环境和软件的分发。
java语言是可移植的,这种可移植性来源于体系结构中立性。
Java语言是解释型的,java程序在java平台上被编译成可移植的字节码格式,在运行时,java平台的java解释器对这些字节码进行解释执行,执行过程中需要的类在联结过程中被载入到运行环境中。
java语言是高性能的,相比于解释型的高级脚本语言,java是高性能的,随着JIT(Just-In-Time)编译技术的发展,java的运行速度越来越接近C++。
Java语言是多线程的,java支持多线程同时执行,并提供了多线程的同步机制。线程是java语言一种特殊对象,它必须由Thread类或者其子(孙l)类来创建。
java语言是动态的,java语言的设计目的之一就是适应于动态变化的环境,java程序需要的类能够动态地被载入到运行环境,也可以通过网络环境载入所需要的类,这有利于软件的升级。另外,java中的类有一个运行时刻的表示,能进行运行时刻的类型检查。
其实java的这几个主要特性的描述已经包含了java相关的许多基础的需要了解的知识点了。
3、JDK与JRE的关系:
JDK:支持Java程序开发的最小环境。JDK=Java程序设计语言+Java虚拟机+JavaAPI类库。
JRE:支持Java程序运行的标准环境。JRE=Java虚拟机+JavaAPI类库中的JavaSEAPI子集。
JVM
关于JVM到底要从什么地方开始写起来,我一度十分犹豫,或者说我到底想要从这次的知识整理中构建起怎么样的概念呢?
java技术的核心就是java虚拟机(JVM,JavaVirtualMachine),所有的java程序都运行在JVM内部。然而JVM是跨语言的平台,它是语言无关的,它并不与java语言绑定,任何编程语言的编译结果满足并包含JVM的内部指令、符号表以及其他的辅助信息,它就是一个有效的字节码文件,能够被虚拟机识别并装载运行。java虚拟机在操作系统之上,并不与硬件直接交互。
JVM的整体体系包含JVM内存区域,JVM内存溢出,类加载,垃圾回收,性能优化。Java虚拟机(JVM)你只要看这一篇就够了!这篇文章中有一个很完善的思维导图,我也应该会从JVM内存区域、类加载、垃圾回收进行切入,但不会太深入,因为许多概念性的东西我在了解过程中并不能完全吃透,还需要再积累,有关JVM更加深入的细节应该后续再进行了解。
1JVM内存区域和JVM内存溢出
如图所示,JVM运行时的内存区域包含程序计数器,本地方法栈,虚拟机栈,方法区,堆,其中程序计数器,本地方法栈,虚拟机栈是线程隔离的,方法区和堆是线程共享的。
1.1程序计数器(ProgramCounterRegister)
内存空间小,线程私有。是当前线程执行字节码的信号指示器,分支、循环、跳转、异常处理、线程恢复等基础功能都需要依赖计数器实现。若线程正在执行一个java方法,这个计数器记录的则是正在执行的虚拟机字节码指令的地址,若线程正在执行一个native方法,那么计数器的值则为Undefined。程序计数器是唯一在java虚拟机规范中没有规定OutOfMemoryError情况的区域。
1.2虚拟机栈(VMStack)
线程私有,生命周期和线程一致,描述的是java方法执行时的内存模型:每个方法执行时都会创建一个栈帧(StackFrame),用于存储局部变量表,操作数栈,动态链接和方法出口等信息,每一个方法的调用到结束都对应着一个栈帧从虚拟机栈入栈到出栈的过程。
局部变量表:存放了编译期可知的各种基本类型(boolean,byte,short,char,int,float,long,double),对象引用(reference类型)和returnAddress类型
StackOverFlowError:线程请求栈深度大于虚拟机所允许的深度。
OutOfMemoryError:如果虚拟机栈可以动态扩展,而拓展时无法申请到足够的内存。
1.3本地方法栈(NativeMethodStack)
它与虚拟机栈的区别是,本地方法栈是为native方法服务的,虚拟机栈是为java方法服务的。
1.4堆(Heap)
在大部分应用中,这是java虚拟器所管理的内存中最大的一块,线程共享,主要用来存储数组和对象的实例。在堆的内部会划分出多个线程私有的分配缓冲区(ThreadLocalAllocationBuffer,TLAB),这些区域可以位于物理上不连续的区域,但要求逻辑上连续。
OutOfMemoryError:如果堆没有内存完成实例分配,且堆无法再拓展时抛出该异常。
1.5方法区(MethodArea)
属于共享内存区域,存储已被虚拟机加载的类信息,常量,静态变量,即时编译器编译后的代码等数据。
运行时常量池:用于存放编译期生成的各种字面量和符号引用。编译器和运行期(String的Intern())都可将常量放入,内存有限,无法申请时抛出OutOfMemoryError。
1.6直接内存
非虚拟机运行时的部分数据区域
2类加载
虚拟机类加载过程是指虚拟机把描述类的数据从.class文件加载到内存,并对数据进行校验、转换解析和初始化,最终形成可以被虚拟机直接使用的java类型。在java中,类的加载、连接和初始化过程都是在程序运行期间完成的。
2.1类加载时期机
在类的生命周期中,类的加载、验证、准备、初始化和卸载这几个阶段是固定的,解析阶段可以在初始化之后再开始(运行时绑定、动态绑定或晚期绑定)。
那么什么情况会触发类的初始化(类加载)呢?
a)在遇到new,getstatic,putstatic和invokestatic这4条字节码时类没有初始化则触发初始化。比如说,当我们使用new创建某个类的实例,调用某个类的静态变量(在编译期已经放入了运行时常量池的除外)和静态方法时需要先把这个类初始化。
b)使用java.lang.reflect包的方法对类进行反射调用的时候。
c)调用子类的初始化时,要求父类也已经初始化
d)虚拟机启动时会先初始化用户指定的主类
e)当使用JDK1.7的动态语言支持时,如果一个java.lang.invoke.MethodHandle实例最后的解析结果REF_getStatic、REF_putStatic、REF_invokeStatic的方法句柄,并且这个方法句柄所对应的类没有进行过初始化,则需先触发其初始化。
类初始化的特殊情况:
a)当子类继承父类,而静态变量定义在父类中,通过子类名调用该静态变量,触发的是定义了该静态变量的父类。
b)若类的静态变量被final修饰是一个常量,调用时不会触发类的初始化
c)当创建某个类的数组,先创建的是数组类,并不会触发该类的初始化
2.2类加载过程
2.2.1加载
a)找到需要加载的类:通过类的全限定名来获取定义此类的二进制流(zip包,网络,运算生成,jsp生成,数据库读取)。
b)将类放入方法区:将二进制流所代表的静态存储结构转化为方法区运行时的数据结构
c)创建访问该类的入口:在内存中生成一个代表这个类的java.lang.Class对象,作为这个类各种数据结构的访问入口。
数组类的特殊性:数组类不通过类加载器创建,而是由虚拟机直接创建,但是数组类的元素类型最终是由类加载器加载的。
a)如果数组类的元素是引用类型,则由递归采用类加载加载
b)若数组类的元素是值类型,则虚拟机会将数组标记为引导类加载器关联
c)数组类的可见性与其元素可见性一致,若其元素是值类型,则数组类可见性默认是public
加载阶段和连接阶段是交叉进行的,只保证开始时间的前后。
2.2.2验证
验证是连接的第一步,主要是确认字节码中包含的信息符合当前虚拟机的要求,主要包括文件格式验证,元数据验证,字节码验证,符号引用验证。
文件格式验证:文件格式验证直接验证字节流,只有通过了文件格式验证的字节流才会存储进方法区,后续三个验证都是针对方法区存储结构,不再直接操作字节流。
元数据验证:进行的是语义校验,保证不存在不符合java语言规范的元数据信息。譬如说这个类是否有父类,继承父类时是否有不符合规范的情况(继承final类、非抽象类未实现全部方法等等)
字节码验证:这是整个校验过程中最复杂的部分,主要目的时通过数据流和控制流的分析,确定程序语义是安全的,符合逻辑的。这个阶段通过对方法体的校验分析,保证类的方法在运行时不会做出危害虚拟机安全的事件。
符号引用验证:符号引用验证发生在将符号引用转变成直接引用的时候,即在连接的第三阶段--解析的时候发生。主要是确保对类自身以外的信息进行匹配性校验。譬如说引用的类是否能通过全限定名找到,是否能在指定类里面找到指定的变量和方法,以及这些方法是否可访问。如果这个阶段发生错误,会抛出java.lang.In