Jvm数据区
[图片上传失败...(image-cd876b-1594521117849)]
- 线程共享区
- 方法区(运行常量池)
主要用于存放堆内存中的一些执行逻辑和变量,类的加载信息,常量,静态变量等一系列信息,和堆不同,方法去不需要频繁gc等操作,又叫永久代 - 堆
Java中的类实例,数组都将存放在堆中,gc发生也是在堆中,堆是Jvm内存中最大的一块内存地址
- 方法区(运行常量池)
- 线程私有区
- 虚拟机栈
虚拟机栈就是线程所私有Java方法的内存区域,包括方法所引用的基本数据类型和引用。
Java方法的使用就是压栈过程,基本数据类型和引用则是存在栈帧中,当方法抛出异常或结束则是出栈的操作。 - 本地方法区
native修饰的非Java方法
本地方法栈也有自己的栈帧等,栈帧里面也相应的有局部变量表,操作数栈,动态链接,出口信息等 - 程序计数器
Jvm的本质是多线程交替的,但是如何保证每一个线程拿到cpu资源的时候可以从结束的地方接着开始,就需要程序技术器来保证
- 虚拟机栈
堆的分类
- Eden
- Survivor0
- Survivor1
- 老年代
首先分配内存到Eden区,当发生Gc之后转移到s0或者s1中(如果s0先被使用,那么下一次则使用s1,总之是为了保留新生代的内容),每一次gc都会给新生代数据记录次数,一般来说超过15次则进入老年代
运行常量池
- 字符串内容
- final修饰的关键字
- 基本数据类型的值
- 符号引用
- 类和结构完全限定名
- 字段名称和描述符
- 方法名称和描述符
Java对象的创建过程
- 类加载
- 分配内存
- 初始化零值
- 设置对象头
- 执行init
类加载过程
类加载过程图-
加载 (所需类的加载过程)
- 通过类的全限定名获取该类的二进制流(通过class全限定名从本地,网络,专有数据库中的jar或者zip中获取.class文件)
- 生成java.lang.class对象作为方法区进入该对象的入口
- 将字节流的存储结构转化到jvm方法区中运行时的数据结构
-
验证 (了解Class字节文件是否符合当前虚拟机要求)
- 文件格式验证: 字节流是否符合class文件规范
- 元数据验证: 是否符合java的语法规范,例如继承接口,是否实现了接口中的方法
- 字节码验证: 数据和控制流验证,保证方法中的类型转换有效
- 符号引用验证: 验证是否可以通过符号引用找到相应的对象和变量
-
准备 (为类的变量分配内存和设置类的初始值(即方法区分配这些变量空间))
- 为变量分配空间
- 为变量初始化赋值的过程,例如int 赋值为 0 ,对象赋值为 null 等
-
特殊 private static final a = 1
正常来说,应该是初始化阶段赋值,但是这个情况下直接在方法去中替换a = 1 ,则在准备阶段就完成赋值
-
解析 (虚拟机将常量池中的符号引用转为直接引用的过程)
- 什么是符号引用?
用一串不会有歧义的符号来标识引用的对象或者是变量 - 什么是直接引用?
正式用指针去引用符号和变量
- 什么是符号引用?
-
初始化
真正按照程序员的意愿去初始化值
分配内存
分配方法
- 指针碰撞
内存空间比较整洁,直接移动指针分配空间 - 空闲链表
“见缝插针”,空间不连续,则找到空闲的地方插入数据
线程安全问题
- 利用CAS不都安的尝试获取内存空间直到成功(目前虚拟机的解决方案)
- TLAB: 为每个线程独自分配一部分空间,且独有,分配的时候优先分配到该空间中
设置对象头
例如Synchronized关键字需要Mark Word中的monitor对象(MonitorExit,MonitorEnter)
对象的访问方式
-
句柄访问
image
reference -> 句柄池中指针 -> 实例数据
-
直接访问
image
reference -> 实例数据
-
句柄和直接访问的优缺点分析
论访问速度直接访问最快,但是如果需要删除的话需要直接删除数据
句柄中则可以直接把句柄值赋null,效率更快
对象死亡的分析方法
- 程序计数法 : 清零以后则可以判断死亡
- 可达性分析法 : 以Gc root为起点 看看各个对象是否可以连接起来,如果连接不起来,被独立则可以判断回收
- 什么是Gc root(GC Roots一般在JVM的栈区域里产生)
- 处于激活状态的线程
- 栈中的对象
- JNI中的变量
- JNI的全局引用
- 对象头的Monitor对象
引用的类型
- 强引用 (大部分是强引用,虚拟机就算oom,也不会回收引用)
- 软引用 (当内存不足的时候,会回收软引用)
- 弱引用 (只要被回收器判定为垃圾,则直接回收,可以用于判断是否被gc回收)
- 虚引用 (虚引用不在乎引用了什么对象,可以说是一种gc标志,必须配合引用队列,可以在对象被回收之前做一些操作)
垃圾回收算法
- 标记清除算法(标记为垃圾后,直接删除)
- 复制算法 (开辟两块空间,直接将为被回收的部分复制到另一部分的空间内)
- 标记整理算法 (先标记,之后移动,使得内存空间干净)
- 分代收集算法 (分为年轻代和老年代,分别使用不同的算法,新生代用复制算法, 老年代用标记整理算法)
垃圾回收器
- Serial (单线程)
单线程回收垃圾,回收的时候需要暂停其它的一切线程
- Parnew (多线程)
对Serial的升级,其它不变,就是变成多线程
- Parallel Scavenge (多线程)
提升吞吐量(程序运行时间/CPU使用时间),提高回收次数,减少回收时间
- CMS (真正意义上的多线程)
- 标记清除算法
- 过程
- 初始标记 (暂停所有线程,标记Gc Root相连的对象)
- 并发标记 (进行可达性分析,标记一系列回收对象)
- 重新标记 (修正并发标记期间用户的修改)
- 并发清除 (开始清理)
- 缺点
- 对CPU资源敏感
- 无法清理浮动垃圾
- 有太多的空间碎片
- G1 (多线程,不区分新生代和老年代)
-
预览图
image - 分区 Region (G1对内存的使用以分区(Region)为单位,而对对象的分配则以卡片(Card)为单位。)
不区分老年代和年轻代都会直接被划分成Region,每个Region还会被细分为若干个大小为512 Byte的Card,卡片还会记录在全局卡片表(Global Card Table)中.
image
不同卡中的对象可能会互相引用,还可能是跨域Region引用,如果存在的话会直接写屏障(并发标记阶段)
+ 分区模型
- 预览图
- 巨型对象 Humongous Region
由于太大会导致分区出现问题,因此直接在老年代中分配空间
- 已记忆集合 Remember Set (RSet) (存在于Region中)
- 记录引用分区内对象的卡片索引
- 内部使用Per Region Table (PRT) 记录使用引用的情况(稀少,细粒度,粗粒度)
+ 稀少 (记录到卡)
+ 细粒度 (记录到region)
+ 粗粒度 (记录到引用的数目)
- 过程
1. 初始标记 (GC Root的标记)
2. 并发标记 (三色标记法开始)
3. 最终标记 (三色标记的最终标记)
4. 筛选回收 (复制清除算法)
- 三色标记
+ white (不可达回收)
+ grey (子对象还未扫描完)
+ black (gc root)
- STAB
> 本质是上对对象的一次快照,快照的内容就是查看对象的颜色(三色标记)以维持并发的效率。 -
双清委派模型
采用双亲委派模式的是好处是Java类随着它的类加载器一起具备了一种带有优先级的层次关系,通过这种层级关可以避免类的重复加载,当父亲已经加载了该类时,就没有必要子ClassLoader再加载一次。
而有了双亲委派模型,黑客自定义的java.lang.String类永远都不会被加载进内存。因为首先是最顶端的类加载器加载系统的java.lang.String类,最终自定义的类加载器无法加载java.lang.String类
双亲委派模型的破坏 (借鉴CSDN博主的文章说的很完整了)
原生的JDBC中Driver驱动本身只是一个接口,并没有具体的实现,具体的实现是由不同数据库类型去实现的。例如,MySQL的mysql-connector-.jar中的Driver类具体实现的。 原生的JDBC中的类是放在rt.jar包的,是由启动类加载器进行类加载的,在JDBC中的Driver类中需要动态去加载不同数据库类型的Driver类,而mysql-connector-.jar中的Driver类是用户自己写的代码,那启动类加载器肯定是不能进行加载的,既然是自己编写的代码,那就需要由应用程序启动类去进行类加载。于是乎,这个时候就引入线程上下文件类加载器(Thread Context ClassLoader)。有了这个东西之后,程序就可以把原本需要由启动类加载器进行加载的类,由应用程序类加载器去进行加载了。
————————————————
版权声明:本文为CSDN博主「Jack老师」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/luoyang_java/article/details/92598142
网友评论