JDK (Java语言、工具及工具API、Java SE API、JVM) 是用于支持 Java 程序开发的最小环境,JRE (Java SE API、JVM) 是支持 Java 程序运行的标准环境。首先看一下 Java 发展史:

版本 时间 内置虚拟机 代表技术
1.0 1996.1.23 纯解释执行的Sun Classic VM JVM、Applet、AWT等。
1.1 1997.2.19 纯解释执行的Sun Classic VM JAR文件格式、JDBC、JavaBeans、RMI、扩展Java语法(内部类、反射)等。
1.2 1998.12.4 第一次内置JIT编译器 (并存过两个虚拟机,附加的HotSpot VM内置JIT编译器,Class VM只能以外挂的形式使用JIT编译器) 拆分为J2SE、J2EE、J2ME三个技术方向,新增了EJB、Java Plug-in、Java IDL、Swing等代表技术,在语言和API级别,添加了strictfp关键词与现在常用的一系列Collections集合类。
1.3 2000.5.8 开始以HotSpot VM为默认虚拟机 主要是新增了一些类库,如数学运算、新的Timer API、2D API、JavaSound类库等。
1.4 2002.2.13 HotSpot VM Java真正走向成熟的一个版本,新增正则表达式、异常链、NIO、日志类、XML解析器、XSLT转换器等。
1.5 2004.9.30 HotSpot VM 改进了语法易用性 (自动装箱、泛型、动态注解、枚举、可变长参数、foreach循环等),在虚拟机和API层面上,这个版本改进了Java的内存模型 (JMM),提供了java.util.concurrent并发包等。
1.6 2006.12.11 HotSpot VM 启动新的命名方式(JavaSE、JavaEE、JavaME),这个版本的改进包括提供动态语言支持,提供编译API和微型HTTP服务器API,同时对JVM内部做了大量改进(锁与同步、垃圾收集、类加载等方面的算法都有很多改进)等。同时JDK开源(也包括HotSpot VM),并建立了OpenJDK组织对这些源码进行独立管理。2009年4月Oracle宣布正式收购Sun。
1.7 2011.7.28 HotSpot VM 提供新的G1收集器,加强对非Java语言的调用支持,升级类加载架构等。开始支持MacOS操作系统,同时还对ARM指令集架构提供了支持。
1.8 2014.3.18 HotSpot VM 新增Lambda表达式,提供函数式接口。
9 2017.9.21 HotSpot VM 平台级模块系统、Linking、JShell 、集合工厂方法、改进的Stream API、私有接口方法、HTTP/2、多版本兼容JAR。

备注:除了极少量的产权代码外,OpenJDK 几乎包括了 SunJDK 的全部代码,在 JDK 7 中,Sun JDK 和 OpenJDK 除了代码文件头的版权注释之外,代码基本上完全一样。

1.Sun HotSpot VM

Sun HotSpot VM 是 Sun JDK 和 OpenJDK 中所带的虚拟机。HotSpot 一开始就是准确式 GC,HotSpot VM 的热点代码探测能力可以通过计数器找出最具有编译价值的代码,然后通知 JIT 编译器以方法为单位进行编译。如果一个方法被频繁调用,或方法中有效循环次数很多,将会分别触发标准编译和 OSR(栈上替换)编译动作。通过编译器与解释器恰当地协同工作,可以在最优化的程序响应时间与最佳执行性能中取得平衡,而且无须等待本地代码输出才能执行程序,即时编译的时间压力也相对减小,这样有助于更多的代码优化技术,输出质量更高的本地代码。

我们平时所提及的高性能 JVM 除了 HotSpot VM,还包括 BEA JRockit VM 和 IBM J9 VM 这类在通用平台上运行的商用虚拟机。

2.JVM运行时数据区

在 JVM 自动内存管理机制的帮助下,Java 不再需要为每一个 new 操作去写配对的 delete/free 代码,而且不容易出现内存泄漏和内存溢出问题。不过也正因为如此,一旦出现了内存泄漏和溢出方面的问题,如果不了解 JVM 是怎样使用内存的,那么排查错误将会是一项艰难的任务。本篇会详细介绍 JVM 运行时的数据区,这些区域的作用、服务对象以及其中可能产生的问题。

JVM 在执行 Java 程序时,会把它所管理的内存划分为不同的数据区,这些区域各有用途,有的区域随着虚拟机进程的启动而存在,有的区域则依赖用户线程的启动/结束而建立/销毁。根据JVM规范 (Java SE 7) 规定,JVM运行时数据区如下:

JVM 技术内幕——走近 JVM、JVM 运行时数据区

- 功能 JVM规范中内存溢出情景
程序计数器 线程私有,空间较小,记录的是正在执行的字节码指令的地址 (行号指示器),字节码解释器就是通过改变这个计数器的值来选取下一条要执行的指令的。每条线程都有一个独立的程序计数器,各条线程之间计数器互不影响,独立存储。 唯一一个没任何OutOfMemoryError情况的区域
虚拟机栈 线程私有,生命周期与线程相同。描述的是Java方法 (字节码) 执行的内存模型,每个方法在执行的同时都会创建一个栈帧 (是方法运行时的基础数据结构) 用于存储局部变量表、操作数栈、动态链接、方法出口等信息,每一个方法从调用直至执行完成的过程,就对应着一个栈帧在JVM栈中入栈到出栈的过程。其中局部变量表存放了编译期可知的各种基本数据类型、对象引用和returnAddress类型(指向了一条字节码指令的地址)。 情况1:如果线程请求的栈深度大于JVM所允许的深度将抛出StackOverflowError异常。 情况2:JVM动态扩展时无法申请到足够的内存将抛出OutOfMemoryError异常。
本地方法栈 线程私有,生命周期与线程相同。本地方法栈功能和虚拟机栈是非常相似的,它们之间的区别只不过是虚拟机栈是为JVM执行Java方法服务,而本地方法栈则为JVM使用到的Native方法服务。 同虚拟机栈。
被所有线程共享,内存中最大的一块。在JVM启动时创建,唯一目的就是存放对象实例以及数组,所有的对象实例都要在这里分配内存。堆是垃圾收集器管理的主要区域,因此也被称为”GC堆”,还可以细分为”新生代”、”老年代”。 如果在堆中没有内存完成实例分配,并且堆也无法扩展时将抛出OutOfMemoryError异常。
方法区 被所有线程共享。用于存储已被JVM加载的类信息、常量、静态常量、即时编译器编译后的代码等数据。堆的一个逻辑部分,也称为Non-Heap(非堆),目的应该是与堆区分开来。对于HotSpot虚拟机,方法区也被称为”永久代”,对于其他虚拟机是不存在”永久代”这个概念的。根据官方信息,现在也有放弃永久代并逐步改为采用Native Memory来实现方法区的规划了,在目前已经发布的JDK1.7的HotSpot中,已经把原本放在永久代的字符串常量池移出。 当方法区无法满足内存分配需求时将抛出OutOfMemoryError异常。

除了这五部分,还包括:

- 功能 JVM规范中内存溢出情景
运行时常量池 方法区的一部分,Class文件中除了有类的版本、字段、方法、接口等描述信息外,还有一项信息是常量池,用于存放编译期生成的各种字面量和符号引用,这部分将在类加载后进入方法区的运行时常量池中存放。 当常量池无法再申请到内存时会抛出OutOfMemoryError异常。
直接内存 不是JVM运行时数据区的一部分,也不是JVM规范中定义的内存区域。在JDK1.4中新加入了NIO类,引入类一种基于通道与缓冲区的I/O方式,它可以使用Native函数库直接分配堆外内存,然后通过一个存储在Java堆中的DirectByteBuffer对象作为这块内存的引用进行操作,这样能在一些场景下显著提高性能,因为避免了在Java堆和Native堆中来回复制数据。 本机内存的分配不会受到Java堆大小的限制,但是会受到本机总内存大小以及处理器寻址空间的限制。服务器管理员在配置JVM参数时,会根据实际内存设置-Xmx等参数信息,但经常忽略直接内存,使得各个内存区域总和大于物理内存限制,从而导致动态扩展时出现OutOfMemoryError异常。

相关文章:

  • 2022-01-08
  • 2021-05-15
  • 2021-04-21
  • 2021-10-16
  • 2021-07-04
猜你喜欢
  • 2021-04-08
  • 2022-12-23
  • 2021-10-16
  • 2022-01-15
  • 2021-03-28
  • 2021-09-01
  • 2021-06-11
相关资源
相似解决方案