企业🤖AI智能体构建引擎,智能编排和调试,一键部署,支持私有化部署方案 广告
[TOC] # JVM运行时数据区 图示一: ![](https://img.kancloud.cn/19/ee/19ee6ef38e84d53da384c2de8a68f209_1635x1055.png) 图示二: ![](https://img.kancloud.cn/57/eb/57eb048854b71175d2d9cde925928022_994x712.png) 图示三: ![](https://img.kancloud.cn/94/df/94df9c2d2b1e95722c98f072b8f78761_888x492.png) 图示四: ![](https://img.kancloud.cn/e9/29/e929e38722ad69367679fe5c4f26e0d3_1657x859.png) Java代码被编译器编译成字节码之后,JVM开辟一片内存空间(也叫运行时数据区),通过类加载器加到到运行时数据区来存储程序执行期间需要用到的数据和相关信息,在这个数据区中,它由以下几部分组成: 1、程序计数器:线程私有,记录着当前线程所执行的字节码的行号指示器,字节码解释器工作时就是通过改变这个计数器的值来选取下一条需要执行的字节码指令。 2、虚拟机栈:线程私有,栈中存放着栈帧,每个栈帧分别对应一个被调用的方法,方法的调用过程对应栈帧在虚拟机中入栈到出栈的过程。每个栈帧中存储有局部变量表、操作数栈、动态链接、方法出口等。 3、本地方法栈:线程私有,和虚拟机栈基本一致,但是为执行native方法而服务的。 4、堆:线程共有,是JVM所管理的内存中最大的一块,用来存储对象实例和数组,在JVM中只有一个堆,但可以处于物理上不连续的内存空间中。由于现在垃圾收集器通常采用分代回收算法,因此堆可以细分为永久世代(permanent)、成熟世代(tenured)、年轻世代(young)。 5、方法区:线程共有,用来存放已经加载的类信息、常量、静态变量以及即时编译器编译后的代码等。 6、运行时常量池:是方法区的一部分,用于存储编译期生成的各种字面量和符号引用。 有一点需要注意的是,当局部变量重新赋值时,基本数据类型的数据本身是不会改变的。并不是在内存中改变字面量内容,而是重新在栈中寻找已存在的相同的数据,若栈中不存在,则重新开辟内存存新数据,并且把要重新赋值的局部变量的引用指向新数据所在地址。 # GC垃圾回收 1、JVM中的栈记录了线程的方法调用,每个线程拥有一个栈,在线程运行的过程中,如果有新的方法调用,那么该线程对应的栈就会增加一个栈帧,栈帧中存储有该方法的参数、局部变量、返回地址。Java的参数和局部变量只能是基本数据类型或者对象引用,因此栈帧中仅存储基本数据类型和引用,而对象引用所引用的对象存储在堆中。方法调用结束时,该栈帧会被删除,参数和局部变量占用的空间也被删除,但对象引用所引用的对象还在堆中。 2、Java对象都存储在堆中,为线程共享的,在某个方法中创建的对象,可以在方法调用结束后,依旧存在于堆中。当该对象不会再被引用时,却仍然占用着堆中的空间,就造成了内存泄漏。 3、Java中的垃圾回收可以自动清空堆中不再使用的对象,防止内存泄漏,有效的使用空闲的内存。 ## 垃圾回收机制算法 垃圾回收算法需要做两件事,一是发现无用信息对象,二是回收被无用对象占用的空间,使该空间可被程序再次使用。 ![](https://img.kancloud.cn/a7/41/a741b169e9171b43480311bd2562b4c2_2408x614.png) ### 引用计数算法 每个对象包含一个计数器,当有新的指向该对象的引用时,计数器加1,当引用移除时,计数器减1,当计数器为0时,认为该对象可以回收。 引用计数法有一个缺陷,无法检测出循环引用。 ```java public class Main { public static void main(String[] args) { MyObject object1 = new MyObject(); MyObject object2 = new MyObject(); object1.object = object2; object2.object = object1; object1 = null; object2 = null; } } ``` 上面例子在最后将对象引用object1和object2都置为null后,他们所引用的对象均不会再被使用,但是两个对象相互引用对方,导致两者的计数器都不为0,因此不能被回收。 ### 可达性分析算法 ![](https://img.kancloud.cn/39/8f/398f2af8904fe969adf358ef7095b46c_345x262.png) 可达性分析算法中,每个对象都有用于标示该对象是否可达的标记信息。以栈和static数据为根,从根出发跟随所有的引用,就可以找到所有的可到达对象。一个可到达对象,一定被根或者其他可到达对象引用。不可达对象就是需要垃圾回收的对象。 #### 标记清除算法(mark and sweep) 垃圾回收启动时,Java程序暂停运行,JVM从根出发,找到所有的可到达对象并进行标记。然后,JVM扫描整个堆,找到剩余的不可到达对象,并清空这些对象占用的内存。 缺点是:1、标记和清除两个过程效率都不高;2、标记清除后会产生大量内存碎片,以后给较大对象分配内存时,找不到足够的连续内存空间。 #### 复制算法 复制算法下,将可用内存划分为大小相等的两块,每次只使用其中一块。当一块使用完时,将还存活的对象复制到另一块内存区域上,再把已使用过的那一块一次清空。复制算法在对象存活率低时回收效率较高,缺点是内存空间的使用率只有一半。 目前商业虚拟机采用这种算法来回收新生代,但并没有按1:1来划分,而是按8:1:1来划分的。也就是一个eden区,一个from区和一个to区,这样只有10%的内存空间会被浪费。 #### 标记整理算法(copy and sweep) 使用复制算法当对象存活率较高时,效率就会变低。标记整理算法在找到所有可达对象并标记后, 将所有存活对象都向内存区域的一端移动,然后清理掉边界以外的所有内存区域。 标记整理算法多用于回收老年代。 #### 分代回收算法(generational collection) ![](https://img.kancloud.cn/4d/5d/4d5d16f899a2b3cfa699580d2699e552_371x180.png) 堆分为三代:永久代(permanent)、老年代(tenured)、新生代(young)。新生代又分为三个区域,分别为:eden区、from区和to区(比例约为8:1:1)。 分代回收流程如下: 1、自从上次垃圾回收后创建的对象叫新生对象,存放于eden区,当eden区没有空间再存放新生对象时,会触发一次Minor GC,JVM会采用复制算法将eden区和from区的所有可到达对象复制到to区,to区紧密排列着复制来的对象,然后清空eden区和from区。经过此番操作后from区和to区互调位置,也就是原来的from区变成to区,to区变成from区。新生对象继续存放在已经清空的eden区中。 2、当发现to区也已经放不下eden区和from区的所有对象时,会将部分对象放到老年代中。 3、即使to区没有满,JVM也会移动生命周期足够久远的对象到老年代。 4、如果老年代存储已满无法放入新的对象时,JVM会触发Major GC,采用标记整理算法对老年代进行垃圾回收。 5、永久代中主要用于存放静态文件,如Java类、方法等,永久代对于垃圾回收没有显著影响,一般回收废弃常量和无用的类。 其中Minor GC发生频率较高;而老年代对象由于存活时间较长,可到达标记率高,因此Major GC发生频率较低。 # 字节码与机器码 ![](https://img.kancloud.cn/7f/ae/7faee11fc0780ec03c9b899f5d4acdcf_775x113.jpg) Java源代码通过编译变成字节码(在Java程序中为.class文件,在Android程序中为.dex文件),然后字节码按照模版中的规则解释为机器码(.oat文件),才可以被CPU解读。