JVM基础及JVM内存结构

本文详细解释了Java虚拟机中的栈、虚拟机栈、局部变量表、操作数栈、返回地址、动态链接等概念,讨论了内存溢出的可能原因,以及虚拟机栈、堆和方法区的内存管理,包括字符串池的位置变迁和内存优化策略。
摘要由CSDN通过智能技术生成

注意:线程只能有一个活动栈帧,就是当前正在执行的方法,即虚拟机栈顶的栈帧。

局部变量表:方法运行中方法参数,局部变量所需要的内存,存储单位称为槽位。

操作数栈:方法运行中方法参数,局部变量运算所需要的内存。

返回地址:方法出口。

动态链接:是找到正确的方法入口,然后才有后来的进栈出栈的执行。

问题0:虚拟机栈是否会出现内存溢出?

会,虚拟机栈是一种栈结构,它有着入栈出栈两种行为。当入栈的栈帧数目大于虚拟机栈的深度时,就会导致java.lang.StackOverFlowError。现实例子比如方法的递归调用,但是递归方向是未知的。还有一种情况是单个栈帧内存过大,直接超出了虚拟机栈剩余内存大小,比如一个方法中定义了过多的变量,此时会报错java.lang.StackOverFlowError。

还有一种情况,就是创建过多的线程,导致没有足够的内存给新线程创建虚拟机栈,此时会报错java.lang.OutOfMemoryError。

问题1:虚拟机栈内存是否越大越好?

JVM可以通过-Xss参数来设置单个线程的虚拟机栈的内存大小,如果单个线程的栈内存设置的越大,则总线程数越少。另外当创建的线程过多时,可能出现OOM。

问题2:虚拟机栈内存是否需要被垃圾回收?

不需要,因为虚拟机栈的生命周期就是线程的生命周期。当线程结束时,虚拟机栈内存被JVM自动回收。相较于堆中对象,虚拟机栈中的数据生命周期更短且更容易判断是否存活。

问题3:方法内的局部变量是否线程安全?

线程安全场景出现的前提是:1.多线程环境 2.多线程共享操作同一个数据

一个方法被多线程执行,

如果方法内的局部变量来自于外部传入(即方法参数),则可能该局部变量线程不安全,

如果方法内的局部变量会作为方法返回值返回,则可能局部变量线程不安全,

如果方法内的局部变量,既不从外部传入,也不传出给外部,则局部变量线程安全。

当然前面三个判断是以局部变量是引用类型为前提的,如果方法内局部变量是基本类型,则一定线程安全。

本地方法栈


和虚拟机栈没有本质区别。

只是虚拟机栈是给Java方法用的,本地方法栈是给Native方法用的。

本地方法栈也会出现StackOverFlowError和OOM。

定义:所有的对象实例和数组实例都存在堆中。

特点

  1. 线程共享,堆中对象都需要考虑线程安全的问题
  1. 堆内存是GC管理的内存区域,可以实现垃圾对象内存回收。
  1. 堆内存是实现分代的,如经典分代将堆分为两块:新生代,老年代
  1. 堆内存可以划分出线程私有的TLAB,即本地线程分配缓冲区,提高内存分配效率,减少内存分配冲突。
  1. 堆内存在物理上可以是不连续的内存组成,但是逻辑上是连续的。
  1. 堆内存既可以是固定大小的,也可以可扩展大小的。可以通过虚拟机参数控制:
  • -Xms 堆初始内存大小
  • -Xmx 堆最大内存大小

注意:当堆内存不足以分配时,就会发生堆内存溢出,即报错java.lang.OutOfMemoryError:Java heap space

方法区


定义:是线程共享的内存区域。方法区是堆的逻辑部分,但是也叫非堆。

作用:存储加载的类型信息,常量,静态变量,即时编译器编译代码的缓存

实现:方法区只是一种概念,对于Hotspot虚拟机,它的具体实现分为两阶段

  1. JDK8:元空间,元空间处于本地内存中
  1. JDK8以前:永久代,永久代处于JVM内存中

设置方法区内存大小的虚拟机参数变化

JDK8:-XX:MaxMetaspaceSize=

JDK8以前:-XX:MaxPermSize=

方法区内存溢出报错变化

JDK8:java.lang.OutOfMemoryError:Metaspace

JDK8以前:java.lang.OutOfMemoryError:PermGen space

问题:是否会存在方法区溢出的场景?

会,大量加载类到方法区,当前流行的Spring,Mybatis框架都使用了cjLib,即底层使用ClassWriter原理自动生成二进制类。

运行时常量池


常量池和运行时常量池的关系:

常量池是class文件,即二进制字节码文件中的Constant Pool信息,当字节码被类加载器加载到方法区后,它的Constant Pool信息就会放入运行时常量池,并把里面的符号地址变为真实地址。

串池和运行时常量池的关系:

运行时常量池中的字符串仅仅是符号,而不是对象。只有对应字符串符号第一次被使用时,才会变成字符串对象,并存入串池。

串池特点:

  1. 字符串对象的延迟加载特性:运行时常量池中的字符串仅仅是符号,只有运行时常量池中的字符串符号第一次被使用时才会变成字符串对象。
  1. 字符串对象的去重特性:当需要使用字符串时,会先使用字符串符号去串池中查找是否由对应的字符串对象,如果有,则直接引用串池中的字符串对象。
  1. 可以使用String::intern方法,主动将串池中还没有的字符串对象放入串池
  1. 字节串+号拼接优化:

对于字符串变量+拼接:底层new StringBuilder().append(“x”).append(“y”).toString();

对于字符串常量+拼接:编译期优化,则直接替换为拼接结果字符串对象。

串池位置:

JDK6:永久代

JDK7:堆

问题:如何验证串池位置

我们可以利用String::intern()方法往串池中加入大量字符串对象,撑爆内存,使之抛出OOM,对于永久代内存溢出报错:java.lang.OutOfMemoryError:PermGen space,对于堆内存溢出报错:java.lang.OutOfMemoryError:Java heap space,所以可以通过内存溢出报错来辨别串池位置。

问题:为什么要将串池位置从永久代换到堆中?

因为Java程序运行中会产生大量的字符串对象,如果不及时回收字符串对象内存,很有可能造成内存溢出,而永久代只有在Full GC时才会进行回收,而堆内存新生代会经常触发Minor GC,代价更小,频率更高。

问题:串池位置的变化对String::intern()方法返回结果的影响?

JDK6时,串池在永久代中,堆中字符串对象调用intern方法后,会尝试去串池中查找是否已存在对应字符串对象,若不存在,则复制堆中字符串对象到串池中,此时串池中复制对象和堆中原对象不是同一个对象。

JDK7时,串池在堆中,堆中字符串对象调用intern方法后,会尝试去串池中查找是否已存在对应字符串对象,若不存在,则串池直接引用堆中字符串对象,此时串池和堆中字符串对象是同一个。

JDK7串池垃圾回收

JDK7时,串池已经放在了堆中,所以串池可以被垃圾回收。

字符串引发的内存问题

字符串对象可以存在堆中(非串池内存),存储在堆中有一个坏处就是允许重复的字符串对象,所以用堆来存储字符串对象,会产生大量相同的字符串对象,从而导致浪费内存。

解决方案:将字符串对象存入串池,串池的数据结构是哈希表,哈希表可以保证不存入重复的字符串,对于重复的字符串对象,对外提供唯一的字符串对象引用。

串池调优

由于串池的数据结构是哈希表,所以字符串对象存入哈希表之前,需要计算其在表中的桶位置,如果对应桶已经有对象了,则比较二者是否相同,如果不相同,则继续和桶下链表节点对象比较。

可以发现在哈希冲突严重的串池中,存入字符串对象的效率很低。所以串池的调优就是针对降低其底层哈希表的哈希冲突。

我们知道当哈希表长度越长时,哈希冲突越少。所以可以增加串池底层哈希表的长度,即增加哈希表桶数目。

-XX:StringTableSize=

直接内存


直接内存的意义

电脑内存条的内存受操作系统直接管理,所以也叫系统内存。

当我们启动Java程序时,操作系统会从系统内存中划分出一部分作为JVM内存。

Java程序只能访问操作JVM内存,不能访问操作系统内存。这就造成了一些繁琐的内存操作。

如阻塞IO,我们需要从磁盘中读取文件到Java程序中,则需要:

读取磁盘文件需要操作系统来做,所以切换到内核态,由操作系统在系统内存中开辟一块缓冲区,然后从磁盘中读取文件到系统缓冲区中,当系统缓冲区写满,则操作系统继续从该系统缓冲区中读取数据,并写入到JVM堆内存的缓冲区中,当JVM堆内存缓冲区写满,操作系统重新从磁盘中读取,周而复始,直到磁盘文件全部读取写入到JVM堆内存中。

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数前端工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Web前端开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上前端开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:前端)

最后

正值金三银四招聘旺季,很多小伙伴都询问我有没有前端方面的面试题,特地整理出来赠送给大家!

资料领取方式:点击这里前往免费获取

学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上前端开发知识点,真正体系化!**

[外链图片转存中…(img-mfRPLr6A-1713544626383)]

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:前端)

[外链图片转存中…(img-Q8lx8sbb-1713544626384)]

最后

正值金三银四招聘旺季,很多小伙伴都询问我有没有前端方面的面试题,特地整理出来赠送给大家!

资料领取方式:点击这里前往免费获取

前端资料图.PNG

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值