【JVM】(内存区域划分 为什么要划分 具体如何分 类加载机制 类加载基本流程 双亲委派模型 类加载器 垃圾回收机制(GC))

文章目录


内存区域划分

为什么要划分

JVM启动的时候会申请到一整个很大的内存区域,JVM是一个应用程序,要从操作系统这里申请内存,JVM就需要根据,把空间,分成几个部分,每个部分各自有不同的功能作用.

具体如何分

存放new出来的对象
方法区/元数据区 ,存放类对象(类加载之后,存放的位置)
存放方法之间的调用关系
程序计数器 存放每个线程,下一条要执行的指令的地址

类加载机制

类加载基本流程

java代码会被编译成.class文件(包含一些字节码),java程序要想运行起来,就需要让jvm读取到这些.class文件,并且把里面的内容,构造成类对象,保存到内存的方法区中。所谓的执行代码就是调用方法。

书上和官方文档把类加载过程分成了5个步骤。

  1. 加载:找到.class文件,打开文件,读取到文件内容。
  2. 验证:.class文件是一个二进制的格式。(某个字节,都是具有特定含义的),就需要验证你当前读到的这个格式是否符合要求。
  3. 准备:给类对象分配内存空间。(只分配内存空间,没有初始化,此时空间上的内存的数值是0,此时如果尝试打印类的static成员,就是全0的)
  4. 解析:针对类对象中包含的字符串常量进行处理,进行一些初始化操作。(java代码中用到的字符串常量在编译之后也会进入到.class文件中)这个过程也叫做:把"符号引用"(文件偏移量)替换成"直接引用"(内存地址)。
  5. 初始化:针对类对象进行初始化,把类对象中需要的各个属性都设置好,还需要初始化好static成员,还需要执行静态代码块,还可能需要加载一下父类。

双亲委派模型

属于类加载中,第一个步骤"加载"过程中国,其中的一个环节。

负责根据全限定类名找到.class文件。

所谓的"双亲委派模型"就是一个查找优先级问题。

类加载器

是JVM中的一个模块,在JVM中内置了三个类加载器。

  1. BootStrap ClassLoader
  2. Extension ClassLoader
  3. Application ClassLoader

上述三个类加载有父子关系,3是子 2是父 3是爷。

双亲委派模型过程:

类加载的过程(找class文件的过程)

  • 给定一个类的全限定类名,形如java.lang.String。
  • 从Application ClassLoader作为入口,开始执行查找的逻辑。
  • Application ClassLoader,不会立即去扫描自己负责的目录(负责的是搜索项目点前目录和第三方库对应的目录)而是把查找的任务,交给他的父亲,Extension ClassLoader。
  • Extension ClassLoader 也不会立即去扫描自己负责的目录,(负责的是JDK中一些扩展的库,对应的目录)而是把查找的任务,交给他的父亲,BootStrap ClassLoader。
  • BootStrap ClassLoader 也不想立即扫描自己负责的目录,(负责的是标准库的目录),也想把任务交给它的父亲,结果发现自己没有父亲,因此BootStrap ClassLoader只能亲自负责扫描,标准库的目录。java.lang.String这种类就能够在标准库中,找到对应的.class文件,就可以进行打开文件,读取文件后续操作。此时查找.class文件的过程就结束了。但是,如果给定的类不是标准库的类,任务仍然会被交给孩子来执行。
  • 如果没有扫描到就会回到Extension ClassLoader。Extension ClassLoader就会扫描负责的扩展库的目录,如果找到,就执行后续的类加载操作,此时查找过程结束,如果没找到,还是把任务交给孩子来执行。
  • 没有扫描到,就会回到Application ClassLoader,Application ClassLoader就会负责扫描当前目录和第三方库的目录,如果找到,就会执行后续的类加载操作,如果没找到,就会抛出一个ClassNotFoundExcepton。

之所以搞这一套流程,主要目的是为了确保,标准库的类,被加载的优先级最高,其次是扩展库,最后是自己写的类和第三方库

垃圾回收机制(GC)

让JVM自行判定,某个内存是否就不再使用了,如果这个内存后面确实不用了,JVM就会自动的把这个内存给回收掉,此时就不必要让程序员自己手动写代码回收。

GC这么好为什么C++不引入GC呢?

  1. 系统缺陷,需要一个/一些特定的线程,不停的扫描内存中的所有的对象,看是否能够回收。此时是需要额外的内存 + CPU资源的。C++要考虑能兼容一些配置特别低的系统。
  2. 效率问题,这样的扫描线程,不一定能够及时的释放内存(扫描总是有一定周期的),一旦同一时刻,出现大量的对象都需要被回收GC产生的负担就会很大,甚至引起整个程序都卡顿。(STW问题 stop the world)。
    GC是垃圾回收,GC回收的目标是内存中的对象,对于Java来说就是new出来的对象。栈里的局部变量,是跟随栈帧的生命周期走的。(方法执行结束,栈帧销毁,内存自然释放)
    静态变量,生命周期就是整个程序,这个始终存在,就意味着静态变量是无需释放的。

GC可以理解成两个大的步骤:

  • 找到垃圾
  1. 引用计数(Python PHP)
    new出来的对象,单独安排一块空间,来保存一个计数器。

    缺陷:
    1.浪费内存

    2.引用计数机制,存在"循环引用"问题。

此时第一个对象和第二对象互相引用,要想使用第一个对象,就需要先拿到第二个对象,如果想要拿到第二个对象,又得先拿到第一个对象,这里就非常像死锁。

  1. 可达性分析(Java)

本质上就是时间换空间这样的手段,有一个/一组线程,周期性的扫描我们代码中所有的对象。(从一些特定的对象触发,尽可能的进行访问的遍历,把所有能够访问到的对象都标记成"可达",反之经过扫描之后,未被标记的对象就是垃圾("不可达")了)

可达性分析出发点很多,不仅仅是所有的局部变量,还有常量池中引用的对象,还有方法区中的静态引用的变量。 这些统称为GCRoots。

可达性分析都是周期性进行的,当前某个对象是否是垃圾,是随着代码的执行,会发生改变。(可达性分析比较消耗系统资源,开销比较大)

  • 回收垃圾

三种基本思路

  1. 标记清除

把对应的对象,直接释放掉,就是标记清楚的方案,这个方案会产生很多的内存碎片,释放内存是为了让别的代码能够申请,申请内存,都是申请到"连续"的内存空间。

优点:实现简单。

缺点:产生不连续的内存碎片,如果程序需要分配一个连续内存的大对象时,就需要提前触发一次垃圾回收。

  1. 复制算法

通过复制的方式把有效的对象,归类到一起,再统一释放剩下的空间。

优点:执行效率高,没有内存碎片的问题。

缺点:空间利用率低,因为复制算法每次只能使用一半的内存。

  1. 标记整理

既能够解决内存碎片化的问题,又能够处理复制算法中利用率低的问题。

类似于顺序表删除元素的搬运操作。搬运的开销仍然很大。

优点:解决了内存碎片问题,比复制算法空间利用率高。

缺点:因为有局部对象移动,所以效率不是很高。

实际上JVM采取的释放思路,是上述基础思路结结合体,让上述方案扬长避短。

分代回收:

相关推荐
李少兄1 小时前
Unirest:优雅的Java HTTP客户端库
java·开发语言·http
此木|西贝1 小时前
【设计模式】原型模式
java·设计模式·原型模式
可乐加.糖2 小时前
一篇关于Netty相关的梳理总结
java·后端·网络协议·netty·信息与通信
s9123601012 小时前
rust 同时处理多个异步任务
java·数据库·rust
9号达人2 小时前
java9新特性详解与实践
java·后端·面试
cg50172 小时前
Spring Boot 的配置文件
java·linux·spring boot
啊喜拔牙2 小时前
1. hadoop 集群的常用命令
java·大数据·开发语言·python·scala
anlogic3 小时前
Java基础 4.3
java·开发语言
非ban必选3 小时前
spring-ai-alibaba第七章阿里dashscope集成RedisChatMemory实现对话记忆
java·后端·spring
A旧城以西3 小时前
数据结构(JAVA)单向,双向链表
java·开发语言·数据结构·学习·链表·intellij-idea·idea