https://zhuanlan.zhihu.com/p/163693285
https://www.bilibili.com/video/BV1aW41167rS?t=3
垃圾回收实质是将已经分配出去的,但却不再使用的内存回收回来,以便能够再次分配。在 Java 虚拟机的语境下,垃圾指的是死亡的对象所占据的堆空间。
垃圾回收器暂时没有学习;
关于Java垃圾回收的面试题:
请讲一下垃圾回收算法?
- 方便程序员
讲一下minor gc和major gc的区别?
- 可达性分析算法
- GC Roots有哪些
讲一下内存分代?
- 标记-清除
- 标记-整理
Java 是一种简单的,面向对象的,适用于网络应用的,平台无关的,解释的,健壮的,安全的,结构自然的,可移植的,高性能的,多线程的,动态的语言。
JVM 是 Java Virtual Machine 的缩写。Java 虚拟机是一种基于计算设备的规范,是一台虚拟机,即虚构的计算机,是通过在实际的计算机上仿真模拟各种计算机功能来实现的。
Java 语言是一种与平台无关的语言,即就是比如说在 Windows 下面写出来的一段 Java 程序,换成 Linux,macOS 系统里面,一样可以正常运行并且输出相同的结果,但是换成 C/C++ 却很难办到,其中完成这跨平台特性的就是 JVM。
JVM 屏蔽了具体操作系统平台的信息(显然,就像是我们在电脑上开了个虚拟机一样),使 Java 程序只需生成在 Java 虚拟机上运行的目标代码(字节码,.class文件
),就可以在多种平台上不加修改地运行。当然,JVM 执行字节码(.class文件
)时实际上还是要解释成具体操作平台的机器语言(机器码)。
通过 JVM,Java 实现了平台无关性,Java 语言在不同平台运行时不需要重新编译,只需要在该平台上部署 JVM就可以了。因而能实现一次编译多处运行。(就像是你的虚拟机也可以在任何安了 VMWare 软件的系统上运行)
如下图中,JVM 有很多系统版本,只要在需要运行 Java 程序的操作系统上安装对应版本的 JVM 就可以运行字节码文件(.class文件
),运行编写的 Java 程序。这就是 Java 跨平台性良好,平台无关特性的来历,JVM 是一个“桥梁”,是一个“中间件”,是实现跨平台的关键,Java 代码首先被编译成字节码文件,再由 JVM 将字节码文件翻译成机器语言,从而达到运行 Java 程序的目的。
需要注意:跨平台的是 Java 程序,不是 JVM。JVM 是用 C/C++ 开发的,是编译后的机器码,不能跨平台,不同平台下需要安装不同版本的 JVM。
JRE:Java Runtime Environment,也就是 JVM 的运行平台,联系平时用的虚拟机,大概可以抽象理解成 JRE = 虚拟机平台 + 虚拟机本体(JVM)。类似于电脑上的 VMWare + 适用于 VMWare 的Ubuntu 虚拟机。这样我们也就明白了 JVM 到底是个什么玩意儿。
JDK:Java Develop Kit,Java的开发工具包,JDK 本体也是 Java 程序,因此运行依赖于 JRE ,由于需要保持JDK的独立性与完整性,JDK 的安装目录下通常也附有 JRE。目前 Oracle 提供的 Windows 下的 JDK 安装工具会同时安装一个正常的 JRE 和隶属于 JDK 目录下的 JRE。
JVM 主要包括:程序计数器(Program Counter),Java 堆(Heap),Java 虚拟机栈(Stack),本地方法栈(Native Stack),方法区(Method Area)。
是一个寄存器,可以看作是代码行号指示器,类似于实际计算机里的程序计数器(PC),用于指示当前命令行以及跳转下一条需要执行的命令。Java 的基础操作以及异常处理等都十分依赖 PC。
JVM 多线程是通过线程轮流切换并分配处理器执行时间的方式来实现的。在一个确定的时刻,一个处理器(或者说多核处理器的一个内核)只会执行一条线程中的命令。因此,为了正常的切换线程,每个线程都会有一个独立的 PC,各线程的 PC 不会互相影响。这个私有的 PC 所占的这块内存即是线程的“私有内存”。
如果线程在执行的是 Java 方法,那么 PC 记录的是正在执行的虚拟机字节码指令的地址。如果正在执行的不是 Java 方法即 Native 方法,那么 PC 的值为 undefined。
PC 的内存区域是唯一的没有规定任何 OutOfMemoryError 的 Java 虚拟机规范中的区域。
PC是线程私有,PC所占有的内存是线程私有
同 PC 一样(从工作流程图里我们可以看到,实际上,PC 也是存在于 JVM Stack 上的),也是线程私有的,生命周期与线程相同。虚拟机栈描述 Java 方法执行的内存模型,每个方法被执行时都会创建一个栈帧(Stack Frame),栈帧会利用局部变量数组存储局部变量(Local Variables),操作栈(Operand Stack),方法出口(Return Value),动态连接(Current Class Constant Pool Reference)等信息。
局部变量数组存储了编译可知的八个基本类型(int, boolean, char, short, byte, long, float, double),对象引用(根据不同的虚拟机实现可能是引用地址的指针或者一个handle),returnAddress 类型。64位的 long 和 double 会占用两个 Slot,其余类型会占用一个 Slot。在编译期间,局部变量所需的空间就会完成分配,动态运行期间不会改变所需的空间。
操作栈在执行字节码指令时会被用到,这种方式类似于原生的 CPU 寄存器,大部分 JVM 把时间花费在操作栈的花费上,操作栈和局部变量数组会频繁的交换数据。
动态连接控制着运行时常量池和栈帧的连接。所有方法和类的引用都会被当作符号的引用存在常量池中。符号引用是实际上并不指向物理内存地址的逻辑引用。JVM 可以选择符号引用解析的时机,一种是当类文件加载并校验通过后,这种解析方式被称为饥饿方式。另外一种是符号引用在第一次使用的时候被解析,这种解析方式称为惰性方式。无论如何 ,JVM 必须要在第一次使用符号引用时完成解析并抛出可能发生的解析错误。绑定是将对象域、方法、类的符号引用替换为直接引用的过程。绑定只会发生一次。一旦绑定,符号引用会被完全替换。如果一个类的符号引用还没有被解析,那么就会载入这个类。每个直接引用都被存储为相对于存储结构(与运行时变量或方法的位置相关联的)偏移量。
对 Java 虚拟机栈这个区域,Java 虚拟机规范规定了两种异常:
本地方法栈如其名字,和 Java Virtual Machine Stack 其实极为类似,只是执行的是 Native 方法,为 Native 方法服务。在 JVM 规范中,没有对它的实现做具体规定。
Java 堆是被所有线程共享的一块区域,在虚拟机启动时创建。 此内存区域的唯一目的就是存放对象实例,几乎所有的对象实例都在这里分配内存(随着技术的发展,已不绝对)。
Java 堆是垃圾收集器管理的主要区域,因而也被称为 GC 堆。收集器采用分代回收法,GC 堆可以分为新生代(Yong Generation)和老生代(Old Generation)。新生代包括 Eden Space 和 Survivor Space。但无论哪个区域,如何划分,存储的都是 Java 对象实例,进一步的划分是为了更好的回收内存或快速的分配内存。
根据 Java 虚拟机规范,堆所在的物理内存区间可以是不连续的,只要逻辑连续就可以。实现时既可以是固定大小,也可以是可扩展的。如果堆无法扩展时,就会抛出 OutOfMemoryError。
方法区和 Java 堆类似,也属于各线程共享的内存区域。 用于存储已被虚拟机加载的类信息,常量,静态变量,即时编译器编译后的代码数据等。它属于非堆区(Non Heap),和 Java 堆区分开。对于存在永久代(Permanent)概念的虚拟机(HotSpot)而言,方法区存在于永久代。Java 虚拟机规范对方法区的规定很宽松,甚至可以不实现 GC。不过并非进入方法区的数据就会永久存在了,这块区域的内存回收主要为常量池的回收和类型的卸载。这个区域的回收处理不善也会导致严重的内存泄漏。
当方法区无法满足内存分配需求时也会抛出 OutOfMemoryError。
用于编译和存储那些被 JIT 编译器编译成原生代码的方法。
类信息存储在方法区,其主要构成为运行时常量池(Run-Time Constant Pool)和方法(Method Code)。
一个编译后的类文件包括以下结构:
结构 | 解释 |
---|---|
magic, minor_version, major_version | 类文件的版本信息和用于编译这个类的 JDK 版本。 |
constant_pool | 类似于符号表,尽管它包含更多数据。下面有更多的详细描述。 |
access_flags | 提供这个类的描述符列表。 |
this_class | 提供这个类全名的常量池(constant_pool)索引,比如 org/jamesdbloom/foo/Bar。 |
super_class | 提供这个类的父类符号引用的常量池索引。 |
interfaces | 指向常量池的索引数组,提供那些被实现的接口的符号引用。 |
fields | 提供每个字段完整描述的常量池索引数组。 |
methods | 指向 constant_pool 的索引数组,用于表示每个方法签名的完整描述。如果这个方法不是抽象方法也不是 native 方法,那么就会显示这个函数的字节码。 |
attributes | 不同值的数组,表示这个类的附加信息,包括 RetentionPolicy.CLASS 和 RetentionPolicy.RUNTIME 注解。 |
运行时常量池是方法区的一部分。Class 文件中有类的版本,字段,方法,接口等描述信息和用于存放编译期生成的各种字面量和符号引用。这部分内容将在类加载后存放到方法区的运行时常量池中。 Java 虚拟机规范对 Class 的细节有着严苛的要求而对运行时常量池的实现不做要求。一般来说除了翻译的 Class,翻译出来的直接引用也会存在运行时常量池中。
运行时常量池具备动态性,即运行时也可将新的常量放入池中。比如 String 类的 intern() 方法。
常量池无法申请到足够的内存分配时也会抛出 OutOfMemoryError。
直接内存并不在 Java 虚拟机规范中,不是 Java 的一部分,但是也被频繁使用并可能导致 OutOfMemoryError。Native 函数库可以直接分配堆外内存,通过存储在 Java 堆里的 DirectDataBuffer 对象作为这块内存的引用进行操作。这样做在一些场景中可以显著提高性能。
直接内存是堆外内存,自然不受 Java 堆大小的限制,但是可能受实体机内存大小的限制。如果内存各部分总和大于实体机的内存时,也会报出 OutOfMemoryError。
本文内容结合
网址 |
---|
https://zhuanlan.zhihu.com/p/274260186 |
https://www.cnblogs.com/cielosun/p/6622983.html |