当前位置:网站首页>你应该知道的 JVM 基础知识
你应该知道的 JVM 基础知识
2022-04-23 06:00:00 【0oIronhide】
Java 程序运行机制步骤
- 首先编写 Java 源代码,源文件的后缀为 .java
- 利用 JVM 编译器将源代码编译成字节码文件,字节码文件的后缀名为 .class
- 由 JVM 解释器运行字节码文件,将字节码文件解释成当前机器能够看懂的二进制数据
- 进行类的转载(加载、链接、初始化)
- 将数据读入到内存中,将不同的数据(变量)放入运行时数据区中的不同区域内

JVM 运行时数据区

- 程序计数器(Program Counter Register):
- 每一个线程都有一个程序计数器,是线程私有的
- 是一个指针,指向方法区的方法字节码,每当执行引擎读取下一条指令时,就计一次数,所占内存非常小
- 在多线程环境下时,某一个时刻一个cpu之会执行一个线程的指令,当要执行另一个线程时,可通过程序计数器来得知下一条需要执行的指令
- JVM 解析器的工作是通过改变这个计数器的值,来选取下一条需要执行的字节码指令,分支、循环、跳转、异常处理、线程恢复等基础功能
- Java 虚拟机栈(Java Virtual Machine Stacks):
- 一个线程有一个栈内存,栈内存包含八大基本数据、对象的引用、方法(栈帧)
- 栈中储存的是一个个栈帧
- 栈帧中保存局部变量和部分计算结果,参与方法的调用与返回,当一个方法被调用时,就创建一个栈帧,方法结束,该栈帧弹出栈,线程一旦结束,栈也就关闭
- 一个栈帧内包含 自己的本地变量数组、方法索引、输入输出参数、本地变量、对象引用、父帧、子帧
- 栈内存不会产生垃圾
- 本地方法栈(Native Method Stack):
- 本地方法栈是为 JVM 调用 Native 方法服务的,Native method(本地方法)特点是与某些硬件交互
- 用native关键字修饰的方法,说明调用该方法超过Java作用范围,Java要去调用底层C语言的方法,凡是Java调用的native方法,会进入JVM的本地方法栈,进而使用JNI调用本地方法接口
- 方法区(Methed Area):
- Java 中所有类的方法信息包含在内,静态变量、常量、类信息(构造方法、接口)、运行时常量池也包含在内
- Java8 之后,方法区在元空间中,元空间在本地内存中
- Java 堆(Java Heap):
*堆内存当中,存在新生区(新生代 PSYoungGen)、养老区(老生代 ParOldGen)
* 一个JVM只有一个堆内存
* 堆内存存储对象的实例、类、方法、常量、变量
*
* 新生区又包含三个区(伊甸区 Eden、幸存者to区、幸存者from区)
* 所有的对象第一次new出来的时候都在伊甸区,当经过GC时,死亡的死亡,存活下来的进入幸存者区
* 当伊甸区的容量满了,新生代会GC,也称为轻量级GC(轻GC)
*
* 最终未死亡的对象会进入养老区
* 当养老区空间满了,也会发生GC,被称为重量级GC(重GC)(Full GC)
* 当新生区与养老区空间都满了,还有新的对象创建出来,则发生OOM内存溢出错误
*
* 元空间(永久区)
* 永久区也被称为非堆
* 在Java8后,此区域常驻内存,JVM方法区也在此区域中,运行时常量池在方法区中
* 此区域是存储JDK自带的class对象,interface元数据、存储类信息与Java运行时环境
* 此区域不会被垃圾回收
* 在JDK1.6之前 永久代,常量池在方法区
* JDK1.7 永久代,但被慢慢淘汰,永久代和方法区都在堆中,常量池在方法区中
* JDK1.8 无永久代,元空间在本地内存中,方法区在元空间中,常量池在方法区中
*
* ps:
* 元空间逻辑上存在,物理上不存在
* 将JVM总内存和最大内存调制1024M时,将新生区与养老区内存总数相加,结果等于最大内存
TLAB(Thread Local Allocation Buffer)
我们说堆是线程共享区域,但会不会有以下问题:
在并发环境下,A线程与B线程同时创建Person对象,但A、B线程的person引用指向同一个实例对象
为解决该问题,对象的内存分配过程就必须进行同步控制。一个HotSpot虚拟机的解决方案:
每个线程在Java堆中预先被分配一小块内存,然后再给对象分配内存的时候,直接在自己这块”私有”内存中分配,当这部分区域用完之后,再分配新的”私有”内存。
这种方案被称之为 TLAB(Thread Local Allocation Buffer)分配。这部分Buffer是从堆中划分出来的,但是是本地线程独享的。
该方案是给每个线程在Eden区分配一块空间,一个线程创建的对象实例就放在自己所属的内存中,当然一个线程所属内存中的对象对于其他线程是可以访问的,在TLAB分配之后,并不影响对象移动到幸存者区或养老区,也不影响其被GC
那如果某个线程的TLAB内存满了怎么办:
1、如果一个对象需要的空间大小超过TLAB中剩余的空间大小,则直接在堆内存中对该对象进行内存分配
2、如果一个对象需要的空间大小超过TLAB中剩余的空间大小,则废弃当前TLAB,重新申请TLAB空间再次进行内存分配
上面两种方案如何选择:
JVM定义了refill_waste参数,可理解为“最大浪费空间”
当请求分配的内存大于refill_waste的时候,选择方案1
若小于refill_waste值,则会废弃当前TLAB,选择方案2
对TLAB进行调优的参数:
- 选择开启或者关闭TLAB,通过设置-XX:+/-UseTLAB参数来指定是否开启TLAB分配。
- TLAB默认是eden区的1%,可以通过选项-XX:TLABWasteTargetPercent设置TLAB空间所占用Eden空间的百分比大小。
- 默认情况下,TLAB的空间会在运行时不断调整,使系统达到最佳的运行状态。如果需要禁用自动调整TLAB的大小,可以使用-XX:-ResizeTLAB来禁用,并且使用-XX:TLABSize来手工指定TLAB的大小。
- TLAB的refill_waste也是可以调整的,默认值为64,即表示使用约为1/64空间大小作为refill_waste,使用参数:-XX:TLABRefillWasteFraction来调整。
- 如果想要观察TLAB的使用情况,可以使用参数-XX+PringTLAB 进行跟踪。
Java 中都有哪些引用类型
- 强引用:发生 gc 的时候不会被回收,例如
Object obj = new Object(),只要 obj 指向堆中的 Object 实例,即使内存不足,该对象也不会被 gc - 软引用:有用但不是必须的对象,在发生内存溢出之前会被回收
- 弱引用:有用但不是必须的对象,在下一次GC时会被回收
- 虚引用(幽灵引用/幻影引用):无法通过虚引用获得对象,用 PhantomReference 实现虚引用,虚引用的用途是在 gc 时返回一个通知
Java 垃圾回收
垃圾回收的区域只发生在堆中
1.当新生代的伊甸区容量满了,引发轻GC,将活下来的对象复制到 幸存者to区(也就是一个空的区域)
2.轻GC之后,伊甸区就为空
3.幸存者区分为to区与from区,如何区别?哪个区为空,哪个就是to区
4.两个区不断移动对象,总会保证有一个区是空的
5.当一个对象经历了15次GC还能存活下来(默认),该对象进入养老区(-XX:MaxTenuringThreshold)此参数可改变对象经历X次GC进入养老区
当养老区满了,会触发重GC,养老区与新生区都会进行清除
但会导致 Stop The World(STW),造成很大的性能开销
STW 就是对堆进行彻底清理,在该过程中,工作线程是无法继续执行的
如果Full GC被触发,这时有大量请求进来,则会被拒绝服务,所以要尽量减少Full GC次数
新生区与养老区的区域、容量设计,也是尽量让对象晚一点进入养老区
Safe Point
Safe Point
发起Full GC的时间点被称为 Safe Point
这个时间点的选定不能太少而使GC时间太长,也不能过于频繁而过分增大运行时开销
Safe Point 主要指的是以下特定位置:
1.循环的末尾
2.方法返回前
3.调用方法的call之后
4.抛出异常的位置
JVM 如何判断当前对象是否要被 GC
- 引用计数器法:
例如 Person p = new Person(“张三”); 这时张三对象的引用数为1
将p置为null,张三对象的引用数为0,就可被清除了
但也会出现循环引用的问题,例如 Person p1 = new Person(“张三”); Person p2 = new Person(“李四”);
将 p1.ref = p2; p2.ref = p1; 使张三李四两实例中的属性ref都指向对方,将p1、p2置为null
这样就形成了循环引用,张三李四两实例的引用数都为1,但没有引用指向他们,JVM无法GC
- 可达性分析算法:
可达性算法的原理是以一系列叫做 GC Root 的对象为起点出发,引出它们指向的下一个节点,再以下个节点为起点,引出此节点指向的下一个结点
通过 GC Root 串成的一条线就叫引用链,直到所有的结点都遍历完毕
如果相关对象不在任意一个以 GC Root 为起点的引用链中,则这些对象会被判断为垃圾,会被 GC 回收。
|
当发生GC时,JVM会给要回收的垃圾对象一次存活的机会,那就是调用对象的finalize()方法,该方法只会调用一次
如果执行finalize()方法后,此对象变成了可达,则不会回收,之后如果该对象再次被GC,则忽略finalize()直接清除
|
GC Root 有以下几类:
虚拟机栈(栈帧中的本地变量表)中引用的对象
方法区中类静态属性引用的对象
方法区中常量引用的对象
本地方法栈中 JNI(即一般说的 Native 方法)引用的对象
JVM 的垃圾回收算法
-
标记-清除算法
该算法是标记可回收的对象,再清除这些对象,该算法实现简单,不需要对象进行移动,但是效率不高,会产生大量不连续的内存碎片。 -
标记-整理算法
该算法标记可回收的对象后将所有存活的对象压缩到内存的一端,使他们紧凑的排列在一起,然后对端边界以外的内存进行回收。回收后,已用和未用的内存都各自一边。该算法解决了内存碎片的问题,但仍需要进行局部对象移动,一定程度上降低了效率。 -
复制算法
该算法把内存空间划为两个相等的区域,每次只使用其中一个区域。垃圾收集时,遍历当前使用的区域,把存活对象复制到另外一个区域中,最后将当前使用的区域的可回收的对象进行回收。该算法实现简单、运行高效,不用考虑内存碎片,但使得内存的利用率不高。
1.轻GC当中使用的算法叫复制算法
2.复制算法所需的两块区域正好符合幸存者to区与from区
3.每次GC,都会将伊甸区与from区存活的对象放入to区,再清除其他区域
4.之后被清除的区域就是to区,存放对象的区就是from区
- 分代回收
堆中的新生代一般用复制算法,因为新生代中会有大量对象死亡,少量对象存活。
老生代中的对象存活时间较长,使用标记清除算法或标记整理算法。
类加载器有哪些
- 根类加载器(Bootstrap ClassLoader):用来加载java核心类库,无法被java程序直接引用(加载 jre 中 jre/lib 下的 jar 文件,例如会加载 rt.jar 下所有的 class 文件,由C++实现,不是 ClassLoader 子类)。
- 扩展类加载器(extensions class loader):它用来加载 Java 的扩展库。Java 虚拟机的实现会提供一个扩展库目录。该类加载器在此目录里面查找并加载 Java 类。
- 系统类加载器(system class loader):它根据 Java 应用的类路径来加载 Java 类。一般来说,Java 应用的类都是由它来完成加载的。可以通过ClassLoader.getSystemClassLoader()来获取它。用户自定义类加载器,通过继承java.lang.ClassLoader类的方式实现。
类装载的执行过程
- 加载:将类的字节码文件读入到内存中,并为之创建一个 java.lang.class 对象的过程
- 链接:
- 校验:确保当前 class 文件中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机的自身安全(文件格式验证、元数据验证、字节码验证、符号引用验证)
- 准备:给类中的静态变量分配内存空间
- 解析:虚拟机将常量池中的符号引用替换成直接引用的过程。符号引用就理解为一个标示,而在直接引用直接指向内存中的地址
- 初始化:对静态变量和静态代码块执行初始化工作,为静态变量赋值
- 使用:执行代码中的操作
- 卸载:JVM 通过 GC 将类信息与相关实例数据从 JVM 内存区域中移除
类加载时机
new一个对象
访问某个类或接口的静态变量,或者对该静态变量赋值
调用类的静态方法
反射(Class.forName(“xxx”))
初始化一个类的子类(会首先初始化子类的父类)
JVM启动时标明的启动类,即文件名和类名相同的那个类
双亲委派加载机制
如果一个类加载器收到了类加载的请求,它首先不会自己去加载这个类,而是把这个请求委派给父类加载器去完成,每一层的类加载器都是如此,这样所有的加载请求都会被传送到顶层的启动类加载器中,只有当父加载无法完成加载请求(它的搜索范围中没找到所需的类)时,子加载器才会尝试去加载类。使用该机制可保证类被成功加载,还可保障一个特定的类被加载一次,避免重复加载。
版权声明
本文为[0oIronhide]所创,转载请带上原文链接,感谢
https://blog.csdn.net/qq_38599840/article/details/106722393
边栏推荐
- 如何使用TiUP部署一个TiDB v5.0集群
- Get DOM element location information by offset and client
- TypeScript(上)
- 【代码解析(7)】Communication-Efficient Learning of Deep Networks from Decentralized Data
- useCenterHook
- leetcode刷题之x的算术平方根
- 【Shell脚本练习】将新加的磁盘批量添加到指定的VG中
- Oracle数据库性能分析之常用视图
- volatile 关键字的三大特点【数据可见性、指令禁止重排性、不保证操作原子性】
- Leak detection and vacancy filling (IX) -- Procedure
猜你喜欢
随机推荐
Offset et client pour obtenir des informations sur l'emplacement des éléments Dom
Passerelle haute performance pour l'interconnexion entre VPC et IDC basée sur dpdk
浏览器工作原理与实践
JS正则匹配先行断言和后行断言
TypeScript(下)
Each traversal usage of tp6
JQ序列化后PHP后台解析
【代码解析(3)】Communication-Efficient Learning of Deep Networks from Decentralized Data
Baidu map coordinates, Google coordinates and Tencent coordinates are mutually transformed
redis 常见问题
JS realizes modal box dragging
file_ get_ Two solutions to content accessing SSL errors
openvswitch 编译安装
DDOS攻击/防御介绍
mysql密码过期的方法
阅读笔记:Meta Matrix Factorization for Federated Rating Predictions
Typescript (lower)
多线程
2021-09-18
Leak detection and vacancy filling (II)



![[ES6 quick start]](/img/9e/4c4be5907c1f7b3485c2f4178b9150.png)





