JVM学习笔记(持续更新)

JDK、JRE、JVM区别?

类加载过程

装载 验证 准备 解析 初始化

类加载器分类

双亲委派模型

如何打破双亲委派模型?

自定义类加载器,集成ClassLoader类重写loadClass,如Tomcat

JVM内存模型

JVM 需要使用计算机的内存,Java 程序运行中所处理的对象或者算法都会使用 JVM 的内

存空间,JVM 将内存区划分为 5 块,这样的结构称之为 JVM 内存模型。

JVM为什么进行内存区域的划分?

随着对象数量的增加,JVM 内存使用率也在增加,如果 JVM 内存使用率达到 100%,

则无法继续运行程序。为了让 JVM 内存可以被重复使用,我们需要进行垃圾回收。为了提高垃圾回收的效率,JVM 将内存区域进行了划分。

JVM内存划分

JVM 按照线程是否共享将内存首先分成两大类
线程独享区

当有当前线程能访问数据的区域,线程之间不能共享,随着线程的创建而创建,随着线程的销毁而被回收。线程存在的时间较短,一般不涉及垃圾回收
线程共享区

所有的线程都可以访问的区域,当线程被销毁的时候,共享区的数据不会被立即回收,需要等待达到垃圾回收的阈值才会进行回收。垃圾回收的优化,主要是优化的线程共享区。

程序计数器

程序计数器会记录当前线程执行指令的内存地址,只占用一小部分区域,只记录一个地址,故我们认为程序计数器不会出现内存溢出的问题。

本地方法栈

Java中有些代码的实现是依赖于其他非Java语言的(C++),本地方法栈存储的是非Java语句执行中产生的数据。一般我们认为本地方法栈不会出现内存溢出的问题。

虚拟机栈

存放当前线程中所声明的变量,包括基本数据类型的数据和引用数据类型的引用。

基本数据类型和引用数据类型划分的标准:

  • 基本数据类型:

  • 四类八种(byte【1字节】、short 【2】、int【4】、 long【8】 、 char【2~3根据编码】、 boolean【1】 、float【4】 、double【8】)

    变量在声明的时候就能确认占用内存的大小。

    引用数据类型将值的引用存放在虚拟机栈中,对象存放在堆内存中,引用数据类型占4个字节

  • 引用数据类型:

    (对象、数组、接口)

    变量在声明的时候,不能取人占用内存的大小。

栈帧

每个线程都会对应一个虚拟机栈,线程的每个方法都会创建一个栈帧,存放本次方法执行过程中所需要的数据。

如果一个线程中有多个方法嵌套调用,虚拟机栈会对栈帧进行压栈和出栈操作,正在执行的方法一定在栈顶,我们只能获取栈顶的栈帧,栈帧在虚拟机栈中先进后出。

栈帧的数据结构
  • 1、局部变量
    存放当前方法的局部变量,基本数据类型存值,引用数据类型存内存地址。
  • 2、操作数栈
    对方法中的变量提供计算的区域
  • 3、常量数据的引用
    常量数据会存放到方法区的常量池中,不管是基本数据类型还是引用数据类型。
  • 4、方法返回值的地址
    方法返回数据会存到计算机内存的寄存器中。
虚拟机栈溢出异常

修改虚拟机栈的内存大小设置栈帧的最大深度:-Xss 虚拟机栈内存大小。

一般栈帧深度达到3000~5000即可。

太小虚拟机栈容易溢出,太大导致每个线程内存占用过大,影响线程数量。

方法区

在 java8 之后,我们把方法区称之为元空间(MetaSpace),方法区在逻辑上属于堆的一部分,但一些具体机制和堆有所区别,如:一些 JVM 的方法区是可以不进行垃圾回收的,关闭 JVM 时才会释放方法区内存。所以方法区还有一个别名叫非堆,目的是和堆分开。
方法区会存储类信息、静态变量、常量(JDK8 之后不存放字符串常量)、本地机器指令。

如果加载大量 class 文件,也会造成方法区内存溢出,如一个 tomcat 运行 20~30 个项目。

堆内存模型

JAVA 对象内存布局

对象头

MarkWord :一系列标记位(哈希码、分代年龄、锁状态标记等),在 64 位系统中占8 字节;
ClassPoint :对象对应的类信息的内存地址,在 64 位系统中占 8 字节。
Length:数组对象特有,表示数组长度,占 4 字节。

实例数据:

包含了对象的所有成员变量,大小由变量类型决定。

byte、boolean:1 字节

short:2 字节

char:2~3 字节

int、float:4 字节

long、double、引用数据类型:8 字节

对其填充

将对象大小填充为 8 字节的整数倍

JVM 内存溢出和垃圾回收机制

为什么要进行堆内存分区:

1、提高垃圾搜索效率;

2、垃圾回收后可以更好的利用内存空间,存放大对象;

3、尽可能减少GC次数

JVM 堆内存的划分


老年代:

对象会优先分配到新生代内存中,每次 GC 后没有回收的对象年龄加 1,年龄到15 还没有被回收,对象会存放到老年代内存中;如果对象较大,超过新生代内存的一半,对象也会存放到老年代区域。
新生代:

为了减少young区垃圾回收后的空间碎片,新生代又分为Eden区和两个Survivor区,且始终有一个 Suvivor 区保持闲置。对象会先存放到 Eden 区当中,Eden 区空间满了之后会进行 young 区的垃圾回收,之后将 young 区所有存活的对象复制到闲置的 Suvivor 区中,并清空 Eden 区和正在使用的 Survivor 区。

YoungGC 和 OldGC

YoungGC

新生代区域的垃圾回收称之为 YoungGC,也叫 MinorGC,Eden 区满后会触发YoungGC

OldGC

老年代区域的垃圾回收称之为 OldGC,也叫 MajorGC,OldGC 非常浪费性能,

所以我们的 JVM 调优要尽可能减少 OldGC 的次数, OldGC 往往伴随着 YoungGC。YoungGC+OldGC = FullGC

问题:

Survivor 区空间并不大,如果满了怎么办?

1、一般情况下 GC 会回收 95%的对象,且超过 15 次 GC 的对象会存放到 old区,所以 Survivor 区不容易满。

2、如果 Survivor 区满了,会触发担保机制,提前将对象存入 Old 区。

为什么需要 Survivor 区?

为了减少垃圾回收带来的空间碎片,空间碎片过多会频繁触发YoungGC。

为什么需要两块 Survivor 区?

为了减少 Survivor 区的空间碎片。

垃圾回收机制

如何判断一个对象是垃圾

引用计数法:

如果要操作对象,必须通过引用来进行。如果一个对象没有任何引用与之关联,则说明该对象基本不太可能在其他地方被使用到。那么这个对象就成为可被回收的对象了。这种方式实现简单,效率较高,但是它无法解决循环引用的问题,因此在 Java 中并没有采用这种方式(Python 采用的是引用计数法)。

达性分析:

以一个GC Root对象作为起点进行搜索,如果在GC Roots和对象之间没有可达路径,以一个GC根对象作为起点进行搜索,如果在GC根和对象之间没有可达路径则称该对象是不可达的。

GC ROOT对象∶

  • 栈帧中的本地变量表中引用的对象。
    我们正在执行的方法是在顶部栈帧中,正在执行的方法所引用的对象必定是很重要的对象
  • 方法区中静态属性引用的对象。
    类中的变量,整个程序都要用的东西,很重要
  • 方法区中常量引用的对象。
    也是要一直使用的对象
  • 本地方法栈中引用的对象.
    存放的是一些非Java语言的对象

常见面试题补充

内存泄漏和内存溢出是一样的概念吗?

不一样,内存泄漏指不再使用的对象无法得到及时的回收,持续占用内存空间,造成内存空间的浪费。内存溢出指程序运行要用到的内存大于能提供的最大内存。内存无法内存泄漏很容易导致内存溢出,内存溢出不一定是内存泄漏导致的。

相关推荐
Somnus陳27 分钟前
软考架构师笔记-计算机系统组成-1
笔记·系统架构
LuH11242 小时前
【论文阅读笔记】IC-Light
论文阅读·笔记
汤姆和佩琦2 小时前
2024-12-25-sklearn学习(20)无监督学习-双聚类 料峭春风吹酒醒,微冷,山头斜照却相迎。
学习·聚类·sklearn
是小菜呀!2 小时前
实验四 触发器
笔记
悲伤小伞2 小时前
C++_数据结构_详解二叉搜索树
c语言·数据结构·c++·笔记·算法
好学近乎知o2 小时前
正则表达式(学习Django过程中可能涉及的)
学习·正则表达式·django
雨中奔跑的小孩2 小时前
爬虫学习案例8
爬虫·学习
jieshenai2 小时前
使用 VSCode 学习与实践 LaTeX:从插件安装到排版技巧
ide·vscode·学习
吴冰_hogan3 小时前
JVM(Java虚拟机)的组成部分详解
java·开发语言·jvm
灰太狼不爱写代码5 小时前
CUDA11.4版本的Pytorch下载
人工智能·pytorch·笔记·python·学习