Java 虚拟机的内存空间分为 5 个部分:
JDK 1.8 同 JDK 1.7 比,最大的差别就是:元数据区取代了永久代。元空间的本质和永久 代类似,都是对 JVM 规范中方法区的实现。不过元空间与永久代之间最大的区别在于:元数据空间并不在虚拟机中,而是使用本地内存。
(1)程序计数器的定义 程序计数器是一块较小的内存空间,是当前线程正在执行的那条字节码指令的地 址。若当前线程正在执行的是一个本地方法,那么此时程序计数器为 Undefined。
(2)程序计数器的作用
(3)程序计数器的特点 是一块较小的内存空间。
由于文章篇幅问题,部门内容将以图片展示,如有小伙伴需完整文档进行查阅观看点赞+关注之后【点击此处】即可获取!!
(1)Java 虚拟机栈的定义 Java 虚拟机栈是描述 Java 方法运行过程的内存模型。
Java 虚拟机栈会为每一个即将运行的 Java 方法创建一块叫做“栈帧”的区域, 用于存放该方法运行过程中的一些信息,如:
(2)压栈出栈过程 当方法运行过程中需要创建局部变量时,就将局部变量的值存入栈帧中的局部变 量表中。
Java 虚拟机栈的栈顶的栈帧是当前正在执行的活动栈,也就是当前正在执行的 方法,PC 寄存器也会指向这个地址。只有这个活动的栈帧的本地变量可以被操 作数栈使用,当在这个栈帧中调用另一个方法,与之对应的栈帧又会被创建,新 创建的栈帧压入栈顶,变为当前的活动栈帧。
方法结束后,当前栈帧被移出,栈帧的返回值变成新的活动栈帧中操作数栈的一 个操作数。如果没有返回值,那么新的活动栈帧中操作数栈的操作数没有变化。
由于 Java 虚拟机栈是与线程对应的,数据不是线程共享的,因此不用关心数据 一致性问题,也不会存在同步锁的问题。
(3)Java 虚拟机栈的特点
(1)对象头
对象头记录了对象在运行过程中所需要使用的一些数据:
对象头可能包含类型指针,通过该指针能确定对象属于哪个类。如果对象是一个 数组,那么对象头还会包括数组长度。
(2)实例数据
实例数据部分就是成员变量的值,其中包括父类成员变量和本类成员变量。
(3)对齐填充
用于确保对象的总长度为 8 字节的整数倍。 HotSpot VM 的自动内存管理系统要求对象的大小必须是 8 字节的整数倍。而 对象头部分正好是 8 字节的倍数(1 倍或 2 倍),因此,当对象实例数据部 分没有对齐时,就需要通过对齐填充来补全。 对齐填充并不是必然存在,也没有特别的含义,它仅仅起着占位符的作用
程序计数器、虚拟机栈、本地方法栈随线程而生,也随线程而灭;栈帧随着方法 的开始而入栈,随着方法的结束而出栈。这几个区域的内存分配和回收都具有确 定性,在这几个区域内不需要过多考虑回收的问题,因为方法结束或者线程结束 时,内存自然就跟随着回收了。而对于 Java 堆和方法区,我们只有在程序运行期间才能知道会创建哪些对象, 这部分内存的分配和回收都是动态的,垃圾收集器所关注的正是这部分内存。
若一个对象不被任何对象或变量引用,那么它就是无效对象,需要被回收。
(1)引用计数法
在对象头维护着一个 counter 计数器,对象被引用一次则计数器 +1;若引用 失效则计数器 -1。当计数器为 0 时,就认为该对象无效了。
引用计数算法的实现简单,判定效率也很高,在大部分情况下它都是一个不错的 算法。但是主流的 Java 虚拟机里没有选用引用计数算法来管理内存,主要是因 为它很难解决对象之间循环引用的问题。
举个栗子对象 objA 和 objB 都有字段 instance,令 objA.instance = objB 并且 objB.instance = objA,由于它们互相引用着对方,导致它们的引用计数 都不为 0,于是引用计数算法无法通知 GC 收集器回收它们。
(2)可达性分析法
所有和 GC Roots 直接或间接关联的对象都是有效对象,和 GC Roots 没有关 联的对象就是无效对象。
GC Roots 是指:
GC Roots 并不包括堆中对象所引用的对象,这样就不会有循环引用的问题。
HotSpot 虚拟机提供了多种垃圾收集器,每种收集器都有各自的特点,虽然我 们要对各个收集器进行比较,但并非为了挑选出一个最好的收集器。我们选择的 只是对具体应用最合适的收集器。
(1)Serial 垃圾收集器(单线程) 只开启一条 GC 线程进行垃圾回收,并且在垃圾收集过程中停止一切用户线程 (Stop The World)。
一般客户端应用所需内存较小,不会创建太多对象,而且堆内存不大,因此垃圾 收集器回收时间短,即使在这段时间停止一切用户线程,也不会感觉明显卡顿。 因此 Serial 垃圾收集器适合客户端使用。
由于 Serial 收集器只使用一条 GC 线程,避免了线程切换的开销,从而简单高 效。
(2)ParNew 垃圾收集器(多线程)
ParNew 是 Serial 的多线程版本。由多条 GC 线程并行地进行垃圾清理。但 清理过程依然需要 Stop The World。
ParNew 追求“低停顿时间”,与 Serial 唯一区别就是使用了多线程进行垃圾收 集,在多 CPU 环境下性能比 Serial 会有一定程度的提升;但线程切换需要额 外的开销,因此在单 CPU 环境中表现不如 Serial。
在高性能硬件上部署程序,目前主要有两种方式:
(1)判断类是否“相等” 任意一个类,都由加载它的类加载器和这个类本身一同确立其在 Java 虚拟机中 的唯一性,每一个类加载器,都有一个独立的类名称空间。
因此,比较两个类是否“相等”,只有在这两个类是由同一个类加载器加载的前 提下才有意义,否则,即使这两个类来源于同一个 Class 文件,被同一个虚拟 机加载,只要加载它们的类加载器不同,那么这两个类就必定不相等。 这里的“相等”,包括代表类的 Class 对象的 equals() 方法、isInstance() 方 法的返回结果,也包括使用 instanceof 关键字做对象所属关系判定等情况。