JVM内存管理机制

目录

JDK,JRE,JVM

Java程序执行流程

 类加载机制

装载

链接

初始化

类加载器

 双亲委派模型

JVM内存模型

 程序计数器

本地方法栈

虚拟机栈

方法区:

 堆内存

JVM垃圾回收机制

堆内存分区

YoungGC

OldGC

如何判断一个对象是否可以被回收

垃圾回收算法


JDK,JRE,JVM

  1. JDK是Java开发环境:JRE+工具(编译器,调试器,其它工具)+类库;
  2. JRE是Java运行环境:JVM+Java解释器;
  3. JVM是Java虚拟机:是一台执行Java程序的机器。

Java程序执行流程

先编译,后解释执行。

文本文件(.java)-->编译器-->.class文件(虚拟指令)-->Java虚拟机(JVM)-->解释为机器指令然后执行

编译的本质是将Java文件翻译成JVM能看懂的class文件。

解释是将字节码二进制流转换为机制指令

 类加载机制

装载

  1. 获取类的全限定域名,把class文件转成二进制流。
  2. 将二进制流中类的描述信息存入方法区,如创建时间,版本等
  3. 将java.lang.Class对象存入堆区。

链接

  1. 验证被加载类的正确性
  2. 在方法区内为静态变量分配空间,并设置初始值(int a = 0;)
  3. 把类的符号引用转为直接引用(把内容变成具体地址)

初始化

        为类的静态变量赋默认值(int a = 10;),并执行静态代码块。

类加载器

存在于JVM中

 双亲委派模型

        如果一个类加载器收到了类加载委托,它并不会自己去加载,而是把这个给父类加载器去执行,如果父类加载器还存在父类加载器,则继续向上委托,依次递归,请求最终被委派到启动类加载器,如果父类加载器可以完成委托,则成功返回,如果不能完成委托,则子类加载器才会尝试自己去加载。

如何打破双亲委派机制:自定义类加载器,继承ClassLoader类,重写loadClass方法。tomcat有自己的类加载器。

JVM内存模型

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

        程序运行时,随着对象数量的增加,JVM内存区使用率也在增加,如果使用率达到100%,程序将无法继续执行,必须进行垃圾回收,为了提高垃圾回收效率,JVM对内存进行区域进行划分。

JVM按照线程是否共享将内存分为两大类:

  1. 线程独享区,只有当前线程可以访问的内存区域,线程之间不可共享,会随线程创建而创建,随线程的销毁而被回收。
  2. 线程共享区,所有线程都可以访问的区域,当线程被销毁的时候,共享区中的数据不会被立即回收,需要等待其使用率达到垃圾回收的阈值,才会进行回收。

 程序计数器

        记录当前线程要执行指令的内存地址,方便线程轮转,只占用一小部分的内存,我们认为该区域不会出现内存溢出问题。

本地方法栈

        存储维护非Java语句(C++)执行过程中产生的数据,一般我们认为不会出现内存问题。

虚拟机栈

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

  1. 基本数据类型:在声明时,能够确认占用内存的大小,因此可直接存放在虚拟机栈。
  2. 引用数据类型:不能确实占用内存的大小,因此存放引用数据类型的地址(4字节),而对象存放在堆内存中。

 栈帧:每个线程都会对应一个虚拟机栈,线程中的每个方法都会创建一个栈帧,存放本次方法执行时需要的数据,其结构如下:

  1. 局部变量:存放当前方法的局部变量,基本数据类型存值,引用数据类型存地址。
  2. 操作数栈:为方法中的变量提供计算区域(计算a=a+1)。
  3. 常量数据的引用:常量数据存在方法区的常量池中,这里只存常量数据地址。
  4. 方法返回值的地址:方法返回值存在计算机内的寄存器中。

虚拟机栈溢出异常:由于栈帧调用的深度太深,会出现虚拟机栈溢出异常,通常发生在递归调用上,可通过在VM Options中修改虚拟机栈内存大小解决,命令为:-Xss虚拟机栈内存。

方法区:

        在逻辑上属于堆区的一部分,但有的JVM的方法区可以不进行垃圾回收,关闭JVM才会释放方法区内存。方法区存储类信息、静态变量、常量(Java 8之后不存字符串常量)、本地机器指令。

JVM执行引擎:JVM采用解释器和即时编译器并存的架构,解释器在程序启动后可以立即执行,但每次执行程序都要将字节码文件的二进制流解释成机器指令,整体执行效率较低。即时编译器不能在程序启动时立即执行,而是将字节码文件编译成机器指令存在方法区,进而执行本地机器指令。

        因此JVM启动时,解释器首先发挥作用,随着时间的推移,即时编译器把越来越多的代码编译成本地机器指令,此时不再需要解释器发挥作用,而直接执行本地机器指令,以获得更好的执行效率。

 堆内存

        JVM将对象存储在堆区,堆区需要的空间是很大的,Java对象在内存中的分布可以分为三个部分:

  1. 对象头:存储标记位(哈希码,分代年龄,状态标记)占8字节,对象对应类信息的内存地址,占8字节,数组对象还会存储其length,占四个字节。
  2. 实例数据:包含对象所有成员变量,大小由变量类型决定。
  3. 填充对齐:将对象大小扩充为8字节的整数倍。
JVM垃圾回收机制

为什么要进行垃圾回收:如果只创建,不回收,会造成堆内存溢出(OOM)异常。

为什么要进行堆内存分区:提高搜索垃圾的效率,不分区会导致内存碎片化,存不了大对象,尽量减少GC次数。 

堆内存分区

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

新生代:为了减少新生代内存空间碎片化的问题,新生代又分为Eden区和两个Survivor区,且始终有一个Survivor区保持闲置。对象会优先存储到Eden区中,Eden区空间满之后进行垃圾回收,剩余对象存入第一个Survivor区中,当第二次GC后,Eden剩余对象被存放到第二个Survivor区中,并将第一个Survivor区中剩余对象存入第二个Survivor区中,如此反复。

YoungGC

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

OldGC

老年代垃圾回收,非常浪费性能(老年代内存区域比较大,且内部没有分区,回收时需要扫描全部区域,如果需要存入较大的对象,老年代区域需要对原有对象进行整理,确保较大对象顺利存入),因此JVM调优应尽量减少OldGC次数。

YoungGC+OldGC=FullGC

Survivor区并不大,满了怎么办:一般一次GC会回收95%的对象,15次没有回收会存入老年代区域,Survivor一般不容易满,如果满了会触发担保机制,提前存入老年代区域。

为什么需要(两块)Survivor区:减少新生代区域的碎片化。

如何判断一个对象是否可以被回收

引用计数法:如果要操作对象,必须通过引用来进行,如果一个对象没有任何引用与之关联,则说明该对象基本不太可能被用到其他地方,那么这个对象就可以被回收,实现起来较简单,但可能会出现循环引用的问题,因此Java中没有采用这种方式(Python中采用)。

可达性分析法:以一个GC Root对象作为起点,如果在GC Root对象和其他对象之间没有可达路径。我们认为该对象不可达,会被回收。

GC Root对象:

  1. 栈帧中的本地变量表中引用的对象(当前方法执行所使用的对象)。
  2. 方法区中静态属性引用的对象。
  3. 方法区中常量引用对象。
  4. 本地方法栈中引用的对象(维护非Java语句执行中产生的对象)。
垃圾回收算法
  1. 标记—清除算法:Old区使用,扫描两遍,第一遍标记,第二遍清除,效率较低,无法解决碎片化问题。
  2. 复制算法:用在Young区的Survivor区,过程为扫描—删除—复制(清空)。

        3.标记—整理算法:比标记清除算法多了一步整理,解决碎片化问题。

垃圾收集器的评判标准:吞吐量↑,停顿时间↓,二者不可得兼。

  1. 串行收集器(已淘汰)
  2. 并行收集器:多个线程同时工作,尽快完成垃圾收集,吞吐量优先。
  3. 并发收集器:用户线程和垃圾回收线程一起工作,停顿时间优先。
常见的垃圾回收器

CMS:并发收集器,基于标记—清理的算法进行垃圾回收,用于OldGC。优点:并发收集,低停顿。缺点:会产生大量碎片,停顿时间虽短但不可控。

G1:并发收集器,从JDK1.7开始支持,能进行OldGC和YoungGC区的垃圾回收,Young区采用复制算法。它没有固定的Old,Young,Eden等区域,而是将内存分为一个个大小相等的格子,每次垃圾回收后,格子用途可以发生改变,提高了内存的灵活性和利用率。

ZGC:从JDK11开始支持,是目前效率最高的垃圾回收器,平均停顿时间为0.05秒。

如何选择垃圾回收器:优先让服务器自己选,如果内存小于100M(或服务器是单核),使用串行收集器,如果要求吞吐量优先,选并行收集器。如果要求停顿时间优先,选并发收集器。

  • 18
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值