java初阶——JVM
JVM指的是Java虚拟机,虚拟机是指通过软件模拟具有完整硬件功能的,运行在一个完全隔离环境的完整计算机系统。
JVM内存区域划分:
JVM 运行时数据区分为 5 块,分为线程私有和线程共享。
线程私有:程序计数器、虚拟机栈、本地方法栈。
1.程序计数器:记录指令执行到哪个地址
2.虚拟机栈:存放栈帧,保存局部变量、保存方法调用关系,栈深度过大抛出 StackOverflowError
3.本地方法栈:用于执行 native 本地方法。
线程共享:堆、方法区。
4.堆(JVM最大空间区域,对象不使用则进行回收):保存new的对象,是GC的主要区域。
5.方法区是逻辑概念,JDK8 使用元空间实现,放在堆外本地内存,存放被虚拟机加载类信息、静态变量、运行时常量池。JDK6‑7 是永久代,放在堆中,JDK8 废除永久代。
Test t=new Test();
1.t是局部变量:引用变量t存虚拟机栈,new出来的对象本体存堆。
2.t是实例成员变量:属于对象的字段,跟着对象本体存放在堆中。3.t是static静态成员变量:引用变量t存元空间(元数据区);new出来的对象本体仍然存堆。
方法区,永久代,元空间是JVM存储类的元数据和相关信息不同概念,有以下区别:
逻辑的概念:方法区:是JVM中一个逻辑上的内存区域,用于存储已经加载的类结构信息、静态变量、常量池、方法字节码。
具体实现:
1.永久代:(JDK8之前)JVM中方法区的实现,是堆内存一部分。
2.元空间:JDK8之后开始引入方法区的实现,存储类的元数据,使用本地内存,不是堆内存存储,元空间大小可以动态调整,提供更加灵活和可扩展存储。
各种常量池区别
| 常量池 | 所在地方 | 归属 | 核心用处 |
|---|---|---|---|
| 类常量池 | 磁盘.class文件 | 磁盘,不在 JVM 内存 | 编译期存放符号信息,压缩 class 文件,为类加载做准备 |
| 运行时常量池 | JDK6‑7:永久代JDK8:元空间(堆外) | 属于方法区 | 类加载后,将符号引用解析为真实内存地址,支撑方法调用、反射 |
| String 字符串常量池 | JDK6:永久代JDK7/8:Java 堆 | 独立池子,不属于方法区 | 字符串对象复用,减少堆内重复字符串,节约内存 |
4. 异常类型
- 虚拟机栈:
StackOverflowError栈溢出;OOM(OutOfMemoryError 内存溢出)- 堆:
OutOfMemoryError- 元空间:
OutOfMemoryError: Metaspace- 程序计数器:无 OOM
JVM类加载:
(.class文件->内存中的类对象)
流程
1.加载找到.class文件
2.验证:解析校验class文件是否合法
3.准备:给类变量(static 修饰的变量)申请内存空间,并且设置默认0值
4.解析:针对字符串常量,进行初始化,将常量池的符号引用替换为直接引用的过程。
5.初始化:类变量进行初始化,也会触发父类的类加载(没有static的其他变量,不属于类加载的流程,只会在创建对像(new)的时候,跟着对象一起初始化)。
类加载时机:1.构造示例2.调用静态属性方法3,使用某个类,且它的父类未进行加载,加载他的父类。
双亲委派模型
描述类加载中,根据全限定类名,找到.class文件的流程
类加载器:JVM中专负责类加载的模块
JVM默认提供三种类加载器
1.BootstrapClassLoader->java标准库的目录
2.ExtensionClassLoader->java扩展库的目录
3.ApplicationClassLoader->java的第三方库/当前的项目

双亲委派过程
进行类加载通过全限定类名,找.class的时候,把ApplicationClassLoader作为入口,然后把加载类的任务,委托给其父亲进行
ExtensionClassLoader也不会立即查找而是委托给父亲,
BootrapClassLoader没有父加载器,只能自己进行类加载,根据类名找到标准库,是否存在匹配的.class文件,BootrapClassLoader没有找到,再将任务还给孩子,ExtensionClassLoader根据类名再扩展库进行查找,找到进行类加载,没找到将任务还给孩子,
ApplicationClassLoader根据类名在第三方目录/项目目录中查找找到进行类加载,没找到抛出异常。
JVM的垃圾回收(GC)
GC回收JVM的区域
| 内存区域 | 内存释放方式 | 是否参与GC |
| 程序计数器 | 线程销毁,自动释放 | 不参与 |
| 栈 | 方法结束,栈帧就结束,随之释放 | 不参与 |
| 元数据区 | 类对象一般不会释放 | 不参与 |
| 堆 | 创建新对象,也有旧对象消亡,垃圾回收器主动回收不再被引用的对象 | 参与 |
GC工作流程
1.找到垃圾(不再被引用的对象)
1)引用计数
每个对象在new的时候都带有一个小的内存空间,保存一个数字,用来记录,有多少个引用指向他,当这个数字为0就成为了垃圾。
问题:
- 消耗内存可能更加多,尤其是当对象本身较小的情况下,使得引用计数的空间比例更加大
- 出现"循环引用的问题"

虽然a和b对象赋值为null,但Test类的引用不为0,而且都无法使用
2)可达性分析
1.以代码的某些特定对象,作为遍历的起点。
2.尽可能遍历,判断对象是否能够访问到
3.每次访问一个对象,就把对象标记成为可达,当完成所有遍历后,就知道哪些是可达以及不可达,不可达就是垃圾
1.如何释放垃圾
1)标记清除把垃圾对象直接进行释放

缺点:内存空间直接进行释放后,空闲的空间不会进行连续,会导致即使有足够的空闲空间但是无法使用
2)复制算法
只使用一边空间,把不是垃圾的对象拷贝到另一边,可以确保空闲空间连续
缺点:1.空间利用率低2.一旦不是垃圾的对象多,复制成本高
3)标记整理

优点:解决内存碎片,保证内存利用率
缺点:搬运数据开销仍然大复制成本高
4)分代回收(java使用)
代(对象GC的轮次)
新时代对象易挂->GC的频次要提高
老年代对象可能继续存在->GC的频次降低

第一轮GC过后,幸存的对象移入幸存区,其他进行回收,新一轮的GC再将幸存的对象移入新幸存区,到一定次数就晋升到老年区。
新生代对象大部分会快速消亡,复制开销小,老年代大部分对象生命周期长,整理的开销也小
更多推荐


所有评论(0)