Java内存空间(学习随笔)

1、程序运行中栈可能会出现两种错误

  • StackOverFlowError: 若栈的内存大小不允许动态扩展,那么当线程请求栈的深度超过当前 Java
    虚拟机栈的最大深度的时候,就抛出 StackOverFlowError 错误。
  • OutOfMemoryError: 如果栈的内存大小可以动态扩展, 如果虚拟机在动态扩展栈时无法申请到足够的内存空间,则抛出OutOfMemoryError异常。

2、JDK1.7和JDK1.8的区别

在这里插入图片描述

在这里插入图片描述

3、Java 虚拟机栈和本地方法栈的区别和联系

Java虚拟机栈和本地方法栈是Java虚拟机(JVM)为每个线程分配的内存区域,用于执行方法的调用和执行。

  1. 区别:
  • Java虚拟机栈:它用于存储Java方法的局部变量、方法参数、返回值和部分方法调用信息。每个线程在运行时都会创建一个对应的Java虚拟机栈,栈的大小可以动态调整。Java虚拟机栈采用后进先出(LIFO)的数据结构,用于支持方法的调用和返回。
  • 本地方法栈:它类似于Java虚拟机栈,但是用于执行本地方法(Native Method)的调用和执行。本地方法是使用其他语言编写的方法,如C或C++,并且通过Java Native Interface(JNI)与Java代码进行交互。本地方法栈也是每个线程独立的,用于支持本地方法的调用和返回。
  1. 联系:
  • Java虚拟机栈和本地方法栈都是为了支持方法的调用和执行而存在的。
  • 它们都是线程私有的,每个线程都有自己的Java虚拟机栈和本地方法栈。
  • Java虚拟机栈和本地方法栈都会随着方法的调用和返回而动态地进行入栈和出栈操作。

总之,Java虚拟机栈和本地方法栈在功能和作用上有所不同,但都是为了支持方法的调用和执行而存在的,并在运行时为每个线程分配独立的内存区域。

4、Java堆空间

在Java虚拟机中,堆是一块用于存储对象实例的内存区域。以下是关于Java虚拟机中堆的位置、作用和分布的梳理:

位置:

  • 堆位于Java虚拟机的内存区域中,是Java应用程序运行时的主要内存区域之一。

作用:

  • 堆用于存储Java程序中的对象实例。所有通过关键字new创建的对象都会在堆上分配内存。
  • 堆是Java中动态内存分配的地方,对象的创建和销毁都在堆上进行。
  • 堆提供了自动的内存管理机制,通过垃圾回收器来自动回收不再使用的对象所占用的内存。

分布:

  • 堆的大小可以通过Java虚拟机的参数进行调整,一般分配给堆的内存大小会根据应用程序的需求进行动态调整。
  • 堆被划分为年轻代和老年代两个区域,以支持不同的垃圾回收算法。
    • 年轻代:年轻代用于存放新创建的对象,它又被分为Eden空间和两个Survivor空间(通常是From和To)。大部分对象在创建后会首先被分配到Eden空间。
    • 老年代:老年代用于存放经过多次垃圾回收仍然存活的对象。一般来说,老年代中的对象生命周期较长。
  • 堆的分布和对象的移动可以根据垃圾回收器的算法而有所不同,比如,新生代的垃圾回收一般采用复制算法,而老年代的垃圾回收一般采用标记-清除算法或标记-压缩算法。

总之,Java虚拟机中的堆是用于存储对象实例的内存区域,具有动态分配和自动回收的特性。它被分为年轻代和老年代,为Java应用程序提供了高效的内存管理机制。

5、方法区

  • 当虚拟机要使用一个类时,它需要读取并解析 Class 文件获取相关信息,再将信息存入到方法区。
  • 方法区会存储已被虚拟机加载的 类信息、字段信息、方法信息、常量、静态变量、即时编译器编译后的代码缓存等数据。

6、字符串常量池

在这里插入图片描述

7、JVM—永久代

  • 在旧版的Java虚拟机中,存在一个称为"永久代"的内存区域。它位于堆内存之外,用于存储一些类的元数据信息,如类的结构、字段、方法、常量池等。永久代的主要功能是存储长时间存在的类信息,这些信息在运行时不会被回收。
  • 然而,永久代存在一些问题。首先,永久代的大小是固定的,无法根据实际需要进行调整,容易导致内存溢出。其次,永久代的垃圾回收机制比较复杂,容易导致性能问题。此外,一些特殊的应用场景下,如动态生成大量类的情况,也容易导致永久代溢出。
  • 因此,在Java 8版本中,JVM引入了元空间(Metaspace)来取代永久代。元空间是位于堆内存之外的内存区域,用于存储类的元数据信息。与永久代不同,元空间的大小可以根据实际需要进行动态调整,避免了永久代的内存溢出问题。此外,元空间的垃圾回收机制也更加简单高效。
  • 总结起来,元空间取代永久代的原因主要是为了解决永久代存在的内存溢出和性能问题,并提供更好的灵活性和可靠性。

8、深入学习链接

9、Java的垃圾回收机制

  • 当Java程序中的对象不再被引用时,垃圾回收机制会自动回收这些对象占用的内存空间,以便为新的对象腾出空间。
  • Java的垃圾回收机制是自动化的,程序员不需要显式地释放内存。

Java的垃圾回收机制主要基于以下两个原则:

  1. 引用计数:每个对象都有一个引用计数器,当有引用指向对象时,计数器加1;当引用停止指向对象时,计数器减1。当计数器为0时,对象被认为是不可达的,可以被回收。

  2. 可达性分析:通过一系列称为"GC Roots"的根对象作为起点,通过对象之间的引用链,判断对象是否可达。如果对象不可达,则可以被回收。

Java的垃圾回收机制采用分代收集算法,将内存分为

  • 新生代(Young Generation)存放新创建的对象
  • 老年代(Old Generation)存放存活时间较长的对象
  • 永久代(PermGen/Metaspace)存放类的元数据信息。

垃圾回收过程主要包括以下几个步骤:

  1. 标记:从GC Roots开始,对所有可达对象进行标记。

  2. 清除:清除所有未被标记的对象,释放其占用的内存空间。

  3. 压缩(可选):将存活的对象往一端移动,整理内存空间,以便为新的对象分配连续的内存空间。

需要注意的是,Java垃圾回收机制是与具体的JVM实现相关的,不同的JVM可能会有不同的垃圾回收算法和策略。一般来说,JVM会根据当前的内存使用情况和系统负载等因素,动态调整垃圾回收的频率和策略,以达到最佳的性能和内存利用率。

10、运行时常量池

JVM中的运行时常量池是Java虚拟机在运行时存储常量的一块内存区域。它是方法区的一部分,用于存放编译器生成的各种字面量和符号引用。运行时常量池的作用是在类加载过程中将字节码文件中的符号引用转化为直接引用,并且在运行期间提供运行时常量的支持。

运行时常量池可以分为两个部分:静态常量池和动态常量池。

静态常量池是指在编译期间确定并存放在class文件中的常量池,包括字面量(如字符串、数字)、符号引用(如类和方法的引用)等。它的作用是存储类、方法和字段等的符号引用,供类加载器在运行时解析这些引用。

动态常量池是在运行时生成的,用于存放在方法区中的常量数据项。它的作用是为JVM提供运行时的常量支持,包括字符串的拼接、动态生成的字节码等。动态常量池是在运行时动态生成的,可以动态地添加和删除常量。

在JVM的内存结构中,每个类的运行时常量池都是独立的,它保存着每个类的常量池信息。当一个类被加载到JVM中时,它的常量池就会被加载到方法区中,并且在类的生命周期内都存在。

总的来说,JVM中的运行时常量池是存储在方法区中的,用于存放编译器生成的各种字面量和符号引用。它提供了运行时常量的支持,并且可以动态地添加和删除常量。运行时常量池在Java程序运行期间起到了重要的作用,对于理解Java虚拟机的内存结构和运行机制非常重要。

11、类加载器的过程

当Java程序执行时,需要将类文件加载到内存中才能被虚拟机执行。
Java类加载过程可以分为以下几个步骤:

加载(Loading):类加载的第一阶段是加载,即将类的字节码加载到内存。这个过程由类加载器(ClassLoader)来完成。在加载阶段,Java会根据类的全限定名(例如:com.example.MyClass)查找并加载对应的字节码文件。

验证(Verification):在验证阶段,虚拟机会对类的字节码进行合法性校验,以确保字节码的正确性和安全性。验证过程包括文件格式验证、元数据验证、字节码验证和符号引用验证等。

准备(Preparation):在准备阶段,虚拟机会为类的静态变量分配内存,并设置默认的初始值。这个阶段不会执行类的静态代码块。

解析(Resolution):在解析阶段,虚拟机会将类中的符号引用转换为直接引用。例如,将方法调用的符号引用转换为实际的方法地址。

初始化(Initialization):在初始化阶段,虚拟机会执行类的静态代码块,对静态变量进行赋值操作。这个阶段是类加载的最后一个阶段。

需要注意的是,类的加载过程是按需进行的,即当程序中使用到某个类时才会进行加载。另外,Java虚拟机规范并未明确规定类加载的具体实现方式,因此不同的虚拟机实现可能会有一些细微的差异。

垃圾回收器(Garbage Collector)是Java虚拟机的一部分,用于自动管理程序运行过程中产生的垃圾对象。垃圾回收的目标是释放不再使用的内存资源,减少内存泄漏和内存溢出的风险。

12、Java中的垃圾回收器

采用了自动内存管理的机制,程序员无需手动释放内存。垃圾回收器会周期性地扫描内存中的对象,并找出那些不再被引用的对象,然后自动回收它们所占用的内存空间。

垃圾回收器的工作原理可以简单描述为以下几个步骤:

  1. 标记(Mark):垃圾回收器会从一组根对象开始,递归遍历所有可达的对象,并标记它们为存活对象。

  2. 清除(Sweep):垃圾回收器会扫描整个堆内存,将没有被标记为存活对象的内存空间进行释放,并回收用于存储这些对象的内存。

  3. 压缩(Compact):在清除阶段完成后,垃圾回收器可能会进行堆内存的整理,将存活的对象统一向一端移动,以便释放一块连续的内存空间。

13、不同的垃圾回收器有不同的实现策略和算法

垃圾回收器的任务是自动管理这些不再使用的对象,释放它们占用的内存空间,以便其他对象可以使用。

  1. 标记-清除算法(Mark and Sweep):这是最基本的垃圾回收算法。它分为两个阶段:标记阶段和清除阶段。

    • 标记阶段:从根对象开始,通过可达性分析算法,垃圾回收器标记所有被引用的对象。被引用的对象被视为存活对象,而未被引用的对象被视为垃圾对象。

    • 清除阶段:在清除阶段,垃圾回收器清除所有未被标记的对象,即垃圾对象。这些垃圾对象所占用的内存空间被释放,以便其他对象使用。但是,这种算法可能会产生内存碎片,因为清除后的空间可能不连续。

  2. 复制算法(Copying):这种算法将内存空间分为两个区域,通常被称为"From"区域和"To"区域。

    • 当进行垃圾回收时,首先将所有存活的对象从"From"区域复制到"To"区域中。

    • 然后,在复制过程中,垃圾回收器会将对象紧密地排列在一起,没有碎片化的问题。

    • 最后,清除"From"区域中的所有对象,这样它就可以被重新使用。这种算法需要额外的空间来存储复制的对象,但是它可以避免内存碎片的问题。

  3. 标记-整理算法(Mark and Compact):这种算法是标记-清除算法的改进版。

    • 标记阶段与标记-清除算法相同,从根对象开始,标记所有被引用的对象。

    • 然后,在整理阶段,垃圾回收器将所有存活的对象向一端移动,并清除边界外的内存空间。这样,被清除的空间可以重新使用,而不会产生内存碎片。

这三种算法在不同的场景下有不同的应用。标记-清除算法适用于内存空间不连续的情况;复制算法适用于对象存活率较低的情况;标记-整理算法适用于内存空间连续但存在内存碎片的情况。垃圾回收器会根据实际情况选择合适的算法来进行垃圾回收。

14、Java类的生命周期

可以分为加载、连接和初始化三个阶段。

  1. 加载(Loading):在加载阶段,Java虚拟机通过类加载器将类的字节码文件加载到内存中,并生成对应的Class对象。加载过程可以分为以下几个步骤:

    • 加载:类加载器根据类的全限定名查找并加载字节码文件。
    • 验证:验证字节码文件的合法性,确保不会危害虚拟机的安全。
    • 准备:为类的静态变量分配内存,并设置默认初始值。
    • 解析:将符号引用转换为直接引用,即将类、方法、字段的引用转换为内存地址的引用。
  2. 连接(Linking):连接阶段主要包括验证、准备和解析三个步骤:

    • 验证:对字节码进行验证,确保其符合Java虚拟机规范。
    • 准备:为类的静态变量分配内存,并设置默认初始值。
    • 解析:将符号引用转换为直接引用,即将类、方法、字段的引用转换为内存地址的引用。
  3. 初始化(Initialization):在初始化阶段,Java虚拟机会执行类的初始化代码,包括静态变量赋值和静态代码块的执行。类的初始化是在首次主动使用该类的时候触发的,例如创建实例、调用静态方法、访问静态变量等。

类的加载、连接和初始化过程是按需进行的,即在需要使用某个类时才会进行相应的加载和初始化操作。同时,Java虚拟机也提供了一些机制来控制类的加载顺序和时机,例如使用静态代码块、延迟加载技术等。

15、双亲委派(Parent Delegation)

是Java类加载器的一种工作机制,它是一种层次化的加载机制,通过委派模式来保证类的加载安全和一致性。

在双亲委派模型中,Java类加载器形成了一个层次结构,从上到下依次为启动类加载器(Bootstrap ClassLoader)、扩展类加载器(Extension ClassLoader)和应用程序类加载器(Application ClassLoader),也叫系统类加载器。每个类加载器都有自己的加载范围,当需要加载一个类时,先将请求传递给上层的类加载器,如果上层的类加载器能够找到并加载该类,就直接返回加载结果;如果上层的类加载器无法找到该类,则将加载请求传递给下层的类加载器。

这种层次结构的好处在于,它可以保证类的加载是从上到下依次进行的,避免了重复加载同一个类,并且可以确保加载的类都是由上层的类加载器加载的,从而防止恶意代码替换核心类库。

具体的加载过程如下:

  1. 当一个类需要加载时,首先由当前类加载器尝试加载。
  2. 如果当前类加载器无法加载该类,则将加载请求委派给上层的父类加载器。
  3. 上层的父类加载器尝试加载该类,如果成功加载,则返回加载结果;如果失败,则继续委派给上层的父类加载器,直到达到顶层的启动类加载器。
  4. 如果顶层的启动类加载器无法加载该类,则会抛出ClassNotFoundException异常。

通过双亲委派模型,Java类加载器可以有效地实现类的共享和隔离,避免了类的冲突和安全问题。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

白夜的月亮

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值