JVM
1. 引言
1.1 什么是JVM?
定义
- Java Vritual Machine - java 程序的运行环境(Java二进制字节码的运行环境)
好处
- 一次编译 ,到处运行
- 自动内存管理,垃圾回收功能
- 数据下标越界越界检查
- 多态
比较
Jvm vs Jre vs JDK
1.2 学习路线
本文主要讲解的是HotSpot VM
HotSpot VM 是目前市面上高性能虚拟机的代表作之一,采用解释器与即时编译器并存的架构
学习主要分为三个部分
此文为第一篇
-
内存与垃圾回收篇
-
JVM概述
-
类加载过程
-
运行时数据区
-
执行引擎
-
内存的分配与回收
-
-
字节码与类的加载篇
-
性能调优篇
1.3 Java代码执行流程
1.4 JVM的架构模型
Java编译器输入的指令流基本上是一种 基于栈的指令集架构 ,另外一种指令集架构则
是 基于寄存器的指令集架构 。
这两种架构之间的区别:
- 基于栈式架构的特点
- 设计和实现更简单,适用于资源受限的系统;
- 避开了寄存器的分配难题:使用零地址指令方式分配。
- 指令流中的指令大部分是零地址指令,其执行过程依赖于操作栈。 指令集更小 ,
编译器容易实现。 - 不需要硬件支持,可移植性更好, 更好实现跨平台
- 基于寄存器架构的特点
- 典型的应用是x86的二进制指令集:比如传统的PC以及Android的Davlik虛
拟机。 - 指令集架构则完全依赖硬件, 可移植性差
- 性能优秀和执行更高效 :
- 花费更少的指令去完成一项操作。
- 在大部分情况下,基于寄存器架构的指令集往往都以一地址指令、二地址指令
和三地址指令为主,而基于栈式架构的指令集却是以零地址指令为主。去由堂。
- 典型的应用是x86的二进制指令集:比如传统的PC以及Android的Davlik虛
由于跨平台性的设计,Java的指令都是根据栈来设计的。不同平台CPU架构不同,所以不能设计为基于寄存器的。优点是跨平台, 指令集小,编译器容易实现,缺点是性能下降,实现同样的功能需要更多的指令。
栈: 跨平台性、指令集小、指令多;执行性能比寄存器差
1.5 JVM的生命周期
虚拟机的启动
Java虚拟机的启动是通过引导类加载器(bootstrap class loader) 创建一个初始类(initial class) 来完成的,这个类是由虚拟机的具体实现指定的。
虚拟机的执行
- 一个运行中的Java虛拟机有着一个清晰的任务:执行Java程序。
- 程序开始执行时他才运行,程序结束时他就停止。
- 执行一个所谓的Java程序的时候,真真正正在执行的是一个叫做Java虚拟机的进程。
虚拟机的退出
有如下的几种情况:
- 程序正常执行结束
- 程序在执行过程中遇到了异常或错误而异常终止
- 由于操作系统出现错误而导致Java虚拟机进程终止
- 某线程调用Runtime类或System类的exit方法,或Runtime类的halt方法,并且Java安全管理器也允许这次exi t或halt操作。
- 除此之外,JNI ( Java Native Interface) 规范描述了用JNI Invocation API来加载或卸载Java虛 拟机时,Java虚拟机的退出情况。
1.6 JVM发展历程
Sun Classic VM
早在1996年Java1.0版本的时候,Sun公司发布了一款名为Sun Classic VM的Java虚拟机,它同时也是世界上第一款商用Java虚拟机,JDK1.4时完全被淘汰。
这款虚拟机内部只提供解释器。
如果使用JIT编译器,就需要进行外挂。但是一旦使用了JIT编译器,JIT就会接管虚拟机的执行系统。解释器就不再工作。解释器和编译器不能配合工作。
现在hotspot内置了此虚拟机。
Exact VM
为了解决上一个虚拟机问题,jdk1.2时,sun提供了此虚拟机。
Exact Memory Management:准确式内存管理
-
也可以叫Non-Conservative/Accurate Memory Management
-
虚拟机可以知道内存中某个位置的数据具体是什么类型。
具备现代高性能虚拟机的雏形
-
热点探测
-
编译器与解释器混合工作模式
只在Solaris平台短暂使用,其他平台上还是classic vm
- 英雄气短,终被Hotspot虚拟机替换
HotSpot VM
HotSpot历史
- 最初由一家名为“Longview Technologies"的小公司设计
- 1997年,此公司被Sun收购; 2009年,Sun公司被甲骨文收购。
- JDK1.3时,HotSpot VM成为默认虚拟机
目前Hotspot占有绝对的市场地位称霸武林。
- 不管是现在仍在广泛使用的JDK6,还是使用比例较多的JDK8中,默认的虚拟机都是
HotSpot - Sun/Oracle JDK和OpenJDK的默 认虚拟机
- 因此本课程中默认介绍的虚拟机都是Hotspot,相关机制也主要是指HotSpot的GC机
制。(比如其他两个商用虛拟机都没有方法区的概念)
从服务器、桌面到移动端、嵌入式都有应用。
名称中的HotSpot指的就是它的热点代码探测技术。
- 通过计数器找到最具编译价值代码,触发即时编译或栈上替换
- 通过编译器与解释器协同工作,在最优化的程序响应时间与最佳执行性能中取得平衡
BEA的JRockit
-
专注于服务器端应用
-
它可以不太关注程序启动速度,因此JRockit内部不包含解析器实现,全部代码
都靠即时编译器编译后执行。
-
-
大量的行业基准测试显示,JRockit JVM是 世界上最快的JVM。
- 使用JRockit产品,客户已经体验到了显著的性能提高(一些超过了70%)和硬件成本的减少(达50%) 。
-
优势:全面的Java运行时解决方案组合
-
JHlockit面向延迟敏感型应用的解决方案JRockit Real Time提供以亳秒或
微秒级的JVM响应时间,适合财务、军事指挥、电信网络的需要
-
MissionContro1服务套件,它是一组以极低的开销来监控、管理和分析生产
环境中的应用程序的工具。
-
-
2008年,BEA被Oracle收购。
-
Oracle表达了整合两大优秀虚拟机的工作,大致在JDK 8中完成。整合的方式是在Hotspot的基础上,移植JRockit的优秀特性。
-
高斯林:目前就职于谷歌,研究人工智能和水下机器人
IBM的 J9
-
全称: IBM Technology for Java Virtual Machine,简称IT4J,内部代号: J9
-
市场定位与Hotspot接近,服务器端、桌面应用、嵌入式等多用途VM
-
广泛用于IBM的各种Java产品。
-
目前,有影响力的三大商用虚拟机之一,也号称是世界上最快的Java虚拟机。
-
2017年左右,IBM发布了开源J9 VM,命名为openJ9,交给Eclipse基金会管理,也称为Eclipse OpenJ9
2. 类加载子系统
将描述类的数据从Class文件加载到内存,并对数据进行校验、转换解析和初始化,最终形成可以被虚拟机直接使用的Java类型,这个过程称为虚拟机的类加载机制。
类的生命周期
一个类型从被加载到虚拟机内存中开始,到卸载出内存为止,它的整个生命周期将会经历加载(Loading)、验证(Verification)、准备(Preparation)、解析(Resolution)、初始化(Initialization)、使用(Using)和卸载(Unloading)七个阶段,其中验证、准备、解析三个部分统称为链接(Linking)
类加载器子系统的作用
-
类加载器子系统负责从文件系统或者网络中加载Class文件,class文件在文件开头有特定的文件标识。
-
ClassLoader只负责class文件的加载,至于它是否可以运行,则由Execution Engine决定。
-
加载的类信息存放于一块称为方法区的内存空间。除了类的信息外,方法区中还会存放运行时常量池信息,可能还包括字符串字面量和数字常量(这部分常量信息是Class文件中常量池部分的内存映射)
ClassLoader角色
- class file 存在于本地硬盘上,可以理解为设计师画在纸上的模板,而最终这个模板在执行的时候是要加载到JVM当中来根据这个文件实例化出n个一模一样的实例。
- class file 加载到JVM中,被称为DNA元数据模板,放在方法区。
- 在.class文件—> JVM —>最终成为元数据模板,此过程就要一个运输工具(类装载器Class Loader), 扮演一个快递员的角色。
2.1 类的加载过程
加载
-
通过一个类的全限定名预取定义此类的二进制字节流
-
将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构
-
在内存中生成一个代表这个类的java. lang.Class对象,作为方法区这个类的各种数据的访问入口
加载阶段结束后,Java虚拟机外部的二进制字节流就按照虚拟机所设定的格式存储在方法区之中了,方法区中的数据存储格式完全由虚拟机实现自行定义,《Java虚拟机规范》未规定此区域的具体 数据结构。类型数据妥善安置在方法区之后,会在 Java堆内存中实例化一个java.lang.Class类的对象, 这个对象将作为程序访问方法区中的类型数据的外部接口。
链接
-
验证(Verify)
-
目的在于确保class文件的字节流中包含信息符合当前虚拟机要求,保证被加载类的正确性,不会危害虚拟机自身安全。
-
主要包括四种验证,文件格式验证,元数据验证,字节码验证,符号引用验证。
-
-
准备(Prepare)
-
为类变量(即静态变量,static修饰的)分配内存并且设置该类变量的默认初始值,即零值。
-
这里不包含用final修饰的static, 因为final在编译的时候就会分配了
-
这里不会为实例变量分配初始化(类还未加载) ,类变量会分配在方法区中,而实例变量是会随着对象一起分配到Java堆中。
-
-
解析(Resolve)
-
将常量池内的符号引用转换为直接引用的过程
-
符号引用(Symbolic References):符号引用以一组符号来描述所引用的目标,符号可以是任何形式的字面量,只要使用时能无歧义地定位到目标即可。
-
直接引用(Direct References):直接引用是可以直接指向目标的指针、相对偏移量或者是一个能间接定位到目标的句柄。
-
-
事实上,解析操作往往会伴随着JVM在执行完初始化之后再执行
-
解析动作主要针对类或接口、字段、类方法、接口方法、方法类型等。对应常量池中的CONSTANT Class info、 CONSTANT Fieldref info、 CONSTANT Methodref info等
-
初始化
直到初始化阶段,Java虚拟机才真正开始执行类中编写的Java程序代码,将主导权移交给应用程序。
-
初始化阶段就是执行类构造器方法
<clinit>()
的过程。-
<clinit>()
此方法不需定义,是javac编译器自动收集类中的所有 类变量的赋值动作 和 静态代码块中的语句(static{}块) 合并而来。类变量指的是static修饰的变量,未用static修饰的是实例变量。编译器收集的顺序是由语句在源文件中出现的顺序决定的
public class Test{ static { a = 10; // 可以赋值 System.out.println(a); // 非法前向引用,不能访问 } static int a = 9; // a初始化为9,因为9的赋值晚于10 }
-
-
此方法不是必需的,如果一个类中没有静态语句块,也没有对类变量的赋值操作,就不会生成
-
<clinit>()
不同于类的构造器。(关联: 构造器是虚拟机视角下的<init>()
) -
若该类具有父类,JVM会保证子类的
<clinit>()
执行前,父类的<clinit>()
已经执行完毕。 -
虚拟机必须保证一个类的
<clinit>()
方法在多线程下被同步加锁。(只会被加载一次)
2.2 类加载器的分类
Java虚拟机设计团队有意把类加载阶段中的“通过一个类的全限定名来获取描述该类的二进制字节流”这个动作放到Java虚拟机外部去实现,以便让应用程序自己决定如何去获取所需的类。实现这个动作的代码被称为“类加载器”(Class Loader)。
在Java虚拟机的角度来看,只存在两种不同的类加载器:一种是启动类加载器(Bootstrap ClassLoader),这个类加载器使用C++语言实现,是虚拟机自身的一部分;另外一种就是其他所有的类加载器,这些类加载器都由Java语言实现,独立存在于虚拟机外部,并且全都继承自抽象类 java.lang.ClassLoader。
无论类加载器的类型如何划分,在程序中我们最常见的类加载器始终只有3个,如下所示:
这四者之间的关系是包含关系,不是上下级关系,也不是继承关系。
启动类加载器
Bootstrap ClassLoader
-
这个类加载使用C/C++语言实现的,嵌套在JVM内部。
-
它用来加载Java的核心库(JAVA HOME/jre/lib/rt.jar.resources. jar或sun. boot . class.path路径下的内容) , 用于提供JVM自身需要的类
-
并不继承自java. lang. ClassLoader,没有父加载器。
-
加载扩展类和应用程序类加载器,并指定为他们的父类加载器。
-
出于安全考虑,Bootstrap启动类加载器只加载包名为java、javax、sun等开头的类
扩展类加载器
Extension ClassLoader
-
Java语言编写,由sun.misc.Launcher$ExtClassLoader实现。
-
派生于ClassLoader类
-
父类加载器为启动类加载器
-
从java .ext . dirs系统属性所指定的目录中加载类库,或从JDK的安装目录的jre/]ib/ext子目录(扩展目录)下加载类库。如果用户创建的JAR放在此目录下,也会自动由扩展类加载器加载。
应用程序类加载器
System ClassLoader
-
java语言编写,由sun.misc. Launcher$AppClassLoader实现
-
派生于ClassLoader类
-
父类加载器为扩展类加载器
-
它负责加载环境变量classpath或系统属性java.class.path 指定路径下的类库
-
该类加载是程序中默认的类加载器,一般来说,Java应用的类都是由它来完成加载
-
通过ClasLoader#getSystemClassLoader()方法可以获取到该类加载器
用户自定义类加载器实现步骤
- 开发人员可以通过继承抽象类java. lang.ClassLoader类的方式,实现自己的类加载器,以满足一些特殊的需求
- 在JDK1.2之前,在自定义类加载器时,总会去继承ClassLoader类并重写loadClass ()方法,从而实现自定义的类加载类,但是在JDK1.2之后已不再建议用户去覆盖loadClass()方法,而是建议把自定义的类加载逻辑写在findClass()方法中
- 在编写自定义类加载器时,如果没有太过于复杂的需求,可以直接继承URLClassLoader类,这样就可以避免自己去编写findClass()方法及.其获取字节码流的方式,使自定义类加载器编写更加简洁。
ClassLoader
是一个抽象类,其后所有的类加载器都继承自ClassLoader(不包括启动类加载器)
2.3 双亲委派机制
Java虚拟机对class文件采用的是按需加载的方式,也就是说当需要使用该类时才会将它的class文件加载到内存生成class对象。而且加载某个类的class文件时,Java虚拟机采用的是双亲委派模式,即把请求交由父类处理,它是一种任务委派模式。
工作原理
- 如果一个类加载器收到了类加载请求它并不会自己先去加载,而是把这个请求委托给父类的加载器去执行
- 如果父类加载器还存在其父类加,载器,则进一步向上委托,依次递归,请求最终将到达项层的启动类加载器
- 如果父类加载器可以完成类加载任务,就成功返回,倘若父类加载器无法完成此加载任务,子加载器才会尝试自己去加载,这就是双亲委派模式。
优势
- 避免类的重复加载
- 保护程序安全,防止核心API被篡改
- 自定义 java.lang.String
沙箱安全机制
自定义String类,但是在加载自定义String类的时候会率先使用引导类加载器加载,而引导类加载器在加载的过程中会先加载jdk自带的文件(rt.jar包中java\lang\String.class),报错信息说没有main方法,就是因为加载的是rt.jar包中的String类。这样可以保证对java核心源代码的保护,这就是沙箱安全机制。
JVM必须知道一个类型是由启动加载器加载的还是由用户类加载器加载的。如果一个类型是由用户类加载器加载的,那么JVM会将这个类加载器的一个引用作为类型信息的一"部分保存在方法区中。当解析一个类型到另一个类型的引用的时候,JVM需要保证这两个类型的类加载器是相同的。|
3. 运行时数据区
- 红色区域:线程共享
- 灰色区域:线程私有
Class Runtime:一个Java程序只有一个Runtime实例
3.1 程序计数器
Program Counter Register(PC寄存器) JVM中的PC寄存器是对物理PC寄存器的一种抽象模拟
介绍
- 一块很小的内存空间,几乎可以忽略不记,运行速度最快的存储区域
- 它是程序控制流的指示器,分支、循环、跳转、异常处理、线程恢复等基础功能都需要依赖这个计数器来完成。
- 字节码解释器工作时就是通过改变这个计数器的值来选取下一条需要执行的字节码指令。
- 如果线程正在执行的是一个Java方法,这个计数器记录的是正在执行的虚拟机字节码指令的地址;如果正在执行的是本地(Native)方法,这个计数器值则应为空(Undefined)。
作用
- 用来存储指向下一条JVM指令的地址
特点
- 线程私有,与线程生命周期一致
- 此内存区域是唯一一个在《Java虚拟机规范》中没有规定任何OutOfMemoryError情况的区域。
问题
-
PC寄存器有什么用?
因为CPU需要不停的切换各个线程,这时候切换回来以后,就得知道接着从哪开始继续执行。
JVM的字节码解释器就需要通过改变PC寄存器的值来明确下一条应该执行什么样的字节码指令。
-
为什么设定为线程私有?
为了能够准确地记录各个线程正在执行的当前字节码指令地址,最好的办法自然是为每一个线程都分配一个PC寄存器,这样一来各个线程之间便可以进行独立计算,从而不会出现相互干扰的情况。
由于CPU时间片轮限制,众多线程在并发执行过程中,任何一个确定的时刻,一个处理器或者多核处理器中的一个内核,只会执行某个线程中的一条指令。
这样必然导致经常中断或恢复,如何保证分毫无差呢?每个线程在创建后,都会产生自己的程序计数器和栈帧,程序计数器在各个线程之间互不影响。
3.2 虚拟机栈
Java Virtual Machine Stacks (Java 虚拟机栈)
栈是运行时的单位,而堆是存储的单位
定义
-
每个线程在创建时都会创建一个虚拟机栈,其内部保存一个个的栈帧(Stack Frame),对应一次次的Java方法调用
-
是线程私有的,生命周期与线程一致
-
每个线程只能有一个活动栈帧,对应着当前正在执行的那个方法(栈的顶部)
-
主管Java程序的运行,保存方法的局部变量(基本数据类型,引用类型的地址)、部分结果,并参与方法的调用和返回
-
注意
- 栈溢出(StackOverflowError, OutOfMemoryError)
- 垃圾回收不涉及栈
- 栈内存分配不是越大越好
- 方法内的局部变量是否线程安全?
- 如果只有一个线程才可以操作此数据,则是线程安全的
- 如果多个线程操作此数据,则此数据是共享数据,如果不考虑同步机制,会存在线程安全问题
- 如果方法内局部变量没有逃离方法的作用访问,则安全
- 外部传入或者返回到外部,则不安全
特点
- 栈是一种快速有效的分配存储方式,访问速度仅次于程序计数器
- 只存在两种操作:入栈和出
- 不存在垃圾回收问题
异常
-
Java虚拟机规范允许Java栈的大小是动态的或者固定不变的
-
如果采用固定大小的栈,可能会出现StackOverflowError异常
栈帧过多导致栈内存溢出、栈帧过大
- 递归循环调用
-
如果采用动态扩展的栈,可能会出现OutOfMemoryError异常
-
栈的存储单位
- 栈中的数据以栈帧的格式存在,每个方法对应一个栈帧
- 栈帧是一个内存区块,是一个数据集
- 内部结构
- 局部变量表(Local Variables)
- 操作数栈(Operand Stack)
- 动态链接(Dynamic Linking)(指向运行时常量池的方法引用)
- 方法返回地址(Return Address)(或方法正常退出或者异常退出的定义)
- 一些附件信息
局部变量表
- 定义为一个数字数组,主要用于存储方法参数和定义在方法体内的局部变量,这些数据类型包括各类基本数据类型、对象引用,以及returnAddress类型
- 局部变量所需的容量大小是在编译期间确定下来的
- 最基本的存储单元是Slot(变量槽)32位的类型占一个slot,64位的类型占用两个slot
- 如果当前帧是由构造方法或者实例方法创建的,那么该对象引用this将会存放在index为0的slot处,其余的参数按照参数表顺序继续排列。
- 栈帧中的局部变量表中的槽位是可以重用的,如果一个局部变量过了其作用域,那么在其作用域之后申明的新的局部变量就很有可能会复用过期局部变量的槽位,从而达到节省资源的目的。
操作数栈
- 在方法执行过程中,根据字节码指令,往栈中写入数据或提取数据,即入栈、出栈
- 主要用于保存计算过程的中间结果,同时作为计算过程中变量临时的存储空间
- 如果被调用的方法带有返回值的话,其返回值将会被压入当前栈帧的操作数栈,并更新PC寄存器中下一条需要执行的字节码指令
- Java虚拟机的解释引擎是基于栈的执行引擎,栈就是操作数栈
- 由于操作数是存储在内存中,因此频繁地执行内存读写会影响执行速度。为了解决这个问题,HotSpot JVM的设计者提出了栈顶缓存(ToS,Top-of-stack Cashing)技术,将栈顶元素全部缓存在物理CPU的寄存器中,以此降低对内存的读写次数,提升执行引擎的执行效率。
代码实例
public class operandTest { public void test() { byte i = 15; int j = 8; int k = i + j; } }
动态链接
- 每一个栈帧内部都包含一个指向运行时常量池中该栈帧所属方法的引用。包含这个引用的目的就是为了支持当前方法的代码能够实现动态链接(Dynamic Linking) 。比如: invokedynamic指令
- 在Java源文件被编译到字节码文件中时,所有的变量和方法引用都作为符号引用(Symbolic Reference)保存在class文件的常量池里。比如:描述一个方法调用了另外的其他方法时,就是通过常量池中指向方法的符号引用来表示的,那么 动态链接的作用就是为了将这些符号引用转换为调用方法的直接引用。
方法的调用
在JVM中,将符号引用转换为调用方法的直接引用与方法的绑定机制相关。
-
静态链接: 当一个字节码文件被装载进JVM内部时,如果被调用的目标方法在编译期可知,且运行期保持不变时。这种情况下将调用方法的符号引用转换为直接引用的过程称之为静态链接。
-
动态链接: 如果被调用的方法在编译期无法被确定下来,也就是说,只能够在程序运行
期将调用方法的符号引用转换为直接引用,由于这种引用转换过程具备动态性,因此也就被称之为动态链接。
虚拟机中提供了以下几条方法调用指令:
-
普通调用指令:
-
invokestatic: 调用静态方法,解析阶段确定唯一方法版本
-
invokespecial: 调用
<init>
方法、私有及父类方法,解析阶段确定唯一方法版本 -
invokevirtual: 调用所有虚方法
-
invokeinterface: 调用接口方法
-
-
动态调用指令:
- invokedynamic: 动态解析出需要调用的方法,然后执行
前四条指令固化在虚拟机内部,方法的调用执行不可人为干预,而invokedynamic指令则支持由用户确定方法版本。其中invokestatic指令和invokespecial指令调用的方法称为非虚方法,其余的(final修饰的除外)称为虚方法。
动态类型语言和 静态类型语言 两者的区别:
就在于对类型的检查是在编译期还是在运行期,满足前者就是静态类型语言,反之是动态类型语言。说的再直白一点就是,静态类型语言是判断变量自身的类型信息;动态类型语言是判断变量值的类型信息,变量没有类型信息,变量值才有类型信息,这是动态语言的一个重要特征。
Lambda的引入使得Java具备了动态类型语言的特性。总体来说还是静态。
方法返回地址
- 存放调用该方法的pc寄存器的值
- 无论通过哪种方式退出,在方法退出后都返回到该方法被调用的位置。方法正常退出时,调用者的pc计数器的值作为返回地址,即调用该方法的指令的下一条指令的地址。而通过异常退出的,返回地址是要通过异常表来确定,栈帧中一般不会保存这部分信息。
线程运行诊断
-
CPU占用过高
- 用top定位哪个进程对cpu的占用过高
- ps H -eo pid,tid,%CPU | grep pid
- jstack pid
- 可以根据线程id找到有问题的线程,进一步定位到问题代码的源码行号
-
程序运行很长时间没有结果
使用jstack pid查看进程的运行情况,可发现死锁
程序发生了死锁
3.3 本地方法栈
Native Method Stacks
Java虚拟机栈用于管理Java方法的调用,而本地方法栈用于管理本地方法的调用。也是线程私有的。
Native Method
简单地讲,一个Native Method就是一个Java调用非Java代码的接口。一个Native Method是这样一个Java方法: 该方法的实现由非Java语言实现,比如C。这个特征并非Java所特有,很多其它的编程语言都有这一机制,比如在C++中,你可以用extern "C"告 知C+ +编译器去调用一个C的函数。
"A native method is a Java method whose implementation is provided by non-java code."
在定义一个native method时,并不提供实现体(有些像定义一个Java interface,因为其实现体是由非java语言在外面实现的。本地接口的作用是融合不同的编程语言为Java所用,它的初衷是融合C/C++程序 。
为什么要使用Native Method?
Java使用起来非常方便,然而有些层次的任务用Java实现起来不容易,或者我们对程序的效率很在意时,问题就来了。
-
与Java环境外交互
有时Java应用需要与Java外面的环境交互,这是本地方法存在的主要原因。 你可以想想Java需要与一些底层系统,如操作系统或某些硬件交换信息时的情况。本地方法正是这样一种交流机制:它为我们提供了一个非常简洁的接口,而且我们无需去了解Java应用之外的繁琐的细节。
-
与操作系统交互
JVM支持着Java语言本身和运行时库,它是Java程序赖以生存的平台,它由一个解释器(解释字节码)和一些连接到本地代码的库组成。然而不管怎样,它毕竟不是一个完整的系统,它经常依赖于一些底层系统的支持。这些底层系统常常是强大的操作系统。 通过使用本地方法,我们得以用Java实现了jre的与底层系统的交互,甚至JVM**的一些部分就是用c写的。 还有,如果我们要使用一些Java语言本身没有提供封装的操作系统的特性时,我们也需要使用本地方法。
-
Sun's Java
Sun的解释器是用c实现的,这使得它能像一些普通的C一样与外部交互 。jre大部分是用Java实现的,它也通过一些本地方法与外界交互。例如:类java. lang.Thread的setPriority() 方法是用Java实现的,但是它实现调用的是该类里的本地方法setPriority0()。这个本地方法是用C实现的,并被植入JVM内部,在Windows 95的平台上,这个本地方法最终将调用win32 SetPriority() API. 这是一个本地方法的具体实现由JVM直接提供,更多的情况是本地方法由外部的动态链接库(external dynamic link library) 提供,然后被JVM调用。
-
现状
目前该方法使用的越来越少了,除非是与硬件有关的应用,比如通过Java程序驱动打印机或者Java系统管理生产设备,在企业级应用中已经比较少见。因为现在的异构领域间的通信很发达,比如可以使用Socket通信,也可以使用web Service等等,不多做介绍。
概述
-
当某个线程调用一个本地方法时,它就进入了一个全新的并且不再受虚拟机限制的世界。它和虛拟机拥有同样的权限。
-
本地方法可以通过本地方法接口来访问 虚拟机内部的运行时数据区 。
-
它甚至可以直接使用本地处理器中的寄存器
-
直接从本地内存的堆中分配任意数量的内存。
-
-
并不是所有的JVM都支持本地方法。因为Java虚拟机规范并没有明确要求本地方法栈的使用语言、具体实现方式、数据结构等。 如果JVM产品不打算支持native方法,也可以无需实现本地方法栈。
- 在Hotspot JVM中, 直接将本地方法栈和虚拟机栈合二为一 。
3.4 堆
heap 线程共享
概述
-
一个JVM实例只存在一个堆内存,堆也是Java内存管理的核心区域。
-
Java堆区在JVM启动的时候即被创建,其空间大小也就确定了。是JVM管理的最大一块内存空间。
- 堆内存的大小是可以调节的。
-
《Java虚拟机规范》规定,堆可以处于物理上不连续的内存空间中,但在逻辑上它应该被视为连续的。
-
所有的线程共享Java堆,在这里还可以划分线程私有的缓冲区(Thread Local Allocation Buffer, TLAB) 。
-
《Java虚拟机规范》中对Java堆的描述是:所有的对象实例以及数组都应当在运行时分配在堆上。(The heap is the run-time data area from which memory for a1ll class instances and arrays is allocated )
- 我要说的是:“几乎” 所有的对象实例都在这里分配内存。从实际使用角度看的。
-
数组和对象可能永远不会存储在栈上,因为栈帧中保存引用,这个引用指向对象或者数组在堆中的位置。
-
在方法结束后,堆中的对象不会马上被移除,仅仅在垃圾收集的时候才会被移除。
-
堆,是GC ( Garbage Collection,垃圾收集器)执行垃圾回收的