JVM运行时内存区
JVM运行时内存区是如何划分的?
方法区(Method Area):存储类的字节码信息、常量池
堆区(Heap Area):存储对象
Java方法栈(Stack Area):所有方法运行时,会创建一个栈帧对象,然后进入栈(方法栈)
本地方法栈(Native Method Stack Area):用C语言写的,方法执行时候,会进入本地方法栈
程序计数器(Pc Register):用于记录当前线程要执行的下一条字节码指定的地址
如何理解方法区(Method Area)?
方法区是逻辑一种定义,是一种规范,可被所有线程共享,不同JVM对方法区的落地实现可能不同,例如在JDK7中方法区称之为持久代,在JDK8中方法区叫元空间(Metaspace),并且这个元空间可以是JVM堆外的一块内存,不占用操作系统为JVM分配的内存。
如何理解JAVA中的堆(Heap),他的构成是怎样的?
Java中的堆是用于存储对象的一块区域,可被所有线程所共享。这块区域又可以分为年轻代和老年代,年轻代又分为伊甸园区和2个幸存区(有一块区域始终是空的)。
JVM虚拟机栈的结构是怎样的?
Java中每个线程都有一个虚拟机栈(Java方法栈)每个方法的执行都会对应着一次入栈(Push)和出栈(Pop)操作,栈中的元素为一个一个的栈帧(Stack Frame)对象,这个栈帧的构成主要有如下几部分:
1、操作数栈(用于执行运算)
2、局部变量表(用于存储方法内部的局部变量)
3、方法的返回值(存储方法的返回值)
4、动态链接(方法中要访问的一些常用池数据,要调用的方法,都会对应一个链接)
5、其他信息
如何理解程序计数器?
程序计数器用于记录当前线程要执行的下一条指令的偏移量地址,每个线程都有一个程序计数器,这个程序计数器也是所有内存中唯一一个不会出现内存溢出的区域
Java对象分配内存的一个基本过程是怎样的?
1、编译器通过逃逸分析(JDK8已默认开启)判定对象是在堆上分配还是在栈上分配
2、假如确定是在堆上分析,则可首先选择TLAB区,检测这块区域是否可以存储这个对象,可以则存储
3、假如TLAB区无法存储新创建的对象,则可以考虑在TLAB区之外的Eden区加锁分配(这里为什么加锁分配:申请分配内存的时候,有可能在java堆的同一个位置申请,这时就需要对拟分配的内存区域进行加锁或者采用CAS等操作,保证这个区域只能分配给一个线程)
4、如果Eden区无法存储对象,则执行Young GC(Minor)-年轻代的垃圾回收
5、假如Eden区执行了Young GC之后,仍然不足以存储对象,则直接分配到老年代。
6、假如老年代也不足以存储这个对象,则执行Full GC,这个过后还不能存储对象则抛出异常。
JVM中年轻代中幸存区设置的比例比较小,可能会产生什么问题?
年轻代的伊甸园区对象越来越多时,会启动Young GC,此时伊甸园区的对象就要拷贝到幸存区。假如这个幸存区比较小,无法存储从伊甸园区拷贝过来的幸存对象,此时这些对象就直接分配到老年代,就会导致老年代的对象越来越多,触发老年代GC的频率就变高,老年代的GC一般为Full GC,这个GC的时间会比较长,会影响系统的吞吐量和执行效率。说明:伊甸园区与两个幸存区的比例通常是8:1:1
假如伊甸园区设置的比例比较小,可能会出现什么问题?
我们创建的对象,大部分都是存储在伊甸园区。假如伊甸园区设置的比例小了,可能会增加GC频率。GC时,用户线程会出现短时间的暂停,这样会影响系统的执行性能。
JVM的堆内存为什么要分为年轻代和老年代?
通过分代设计,减少GC的空间范围,提高GC效率。
JVM调优中为什么推荐初始堆大小和最大堆大小要设置成一样的(-Xms2048m -Xms2048m)?
避免程序在运行过程中,因对象的多少或GC后内存的变化,进而导致的内存大小的调整,这个内存大小的调整的过程可能会带来更大的系统开销。(阿里的开发规范中)
什么情况下对象可能会直接存储到老年代?
1、创建的对象比较大,年轻代没有空间存储这个对象。
2、年轻代GC时,活着对象比较多,幸存区存不下,此时这些对象可能会存储到老年代。
3、经过多次GC,没有被回收的对象,随着年龄的增加(默认是15)可能会移动到老年代。
Java中所有的对象都分配在堆上的,对吗?
随着技术的进步,这个说法已经不是那么准确了,对象可以分配在栈上了(小对象、未逃逸的对象可以分配在栈上)。
Java中的逃逸分析如何理解,可以解决什么问题?
逃逸分析本质是一种数据分析算法,基于这个算法判定对象是否发生了逃逸,未逃逸的对象可以直接分配在栈上、也可以执行标量替换。这样可以有效的减少对象在堆上的分配,进而减少阻塞、GC频率,提高执行效率。
如何理解Java中的标量替换技术,为什么要进行标量替换?
标量替换是一种将对象打散(将对象中成员以局部变量的方式进行设计)分配到栈上的技术,减少对象在堆中的创建次数,进而降低GC频率,提高其性能。
什么是内存溢出以及导致内存溢出的原因?
内存中剩余的内存空间不足以分配给新的内存请求,此时就会出现内存溢出。内存溢出可能会导致系统崩溃,具体可能导致内存溢出的原因有:
1、创建的对象太大。
2、创建的对象太多,又有大量的内存泄漏。
3、方法区的类太多了,没有足够的空间存储一些新的类型了。
4、方法出现了无限递归调用,可能会导致栈内存溢出。
什么是内存泄漏以及导致内存泄漏的原因?
程序中的对象在使用完毕之后,对象占有的内存空间,没有及时得到释放,一直占着内存空间,这个现象就称之为内存泄漏。常见的内存泄漏有:
1、缓存使用不当(例如缓存中对对象的引用都是强引用)。
2、内部类的使用不当(例如实例内存类会默认保存外部类引用)。
3、大量的IO连接操作没有及时得到关闭。
4、大量的使用static变量(这个的变量的生命周期不依赖于变量所在的类对象)。
Java中的四大引用有什么特点?
强引用:当内存不足时,JVM开始垃圾回收,对于强引用的对象,就算是出现了OOM也不会对该对象进行回收。强引用是我们最常见的普通对象引用,只要还有强引用指向一个对象 ,就能表明这个对象还“活着”,垃圾回收器就不会碰这些对象。
软引用:软引用是一种相对弱化了一些的引用,需要用 java.lang.ref.SoftReference 类来实现,可以让对象豁免一些垃圾收集。对于只有软引用(还有用、但非必须)的对象来说,
1)当系统内存充足时他不会被回收。
2)当系统内存不足时他会被回收。
这个特性很适合用来实现缓存:比如网页缓存、图片缓存等... 内存够用就保留,不够用就回收,可以很好地来解决OOM问题。
软引用可以和一个引用队列(ReferenceQueue)联合使用,如果软引用所引用的对象被JVM回收,这个软引用就会被加入到与之关联的引用队列中。
弱引用:弱引用也是用来描述非必须对象,但是他的强度比软引用更弱一些。
如果你创建了一个仅持有弱引用的对象,那么下一次垃圾回收发生的时候,无论当前内存是否足够,这个对象都会被回收掉。在JDK1.2之后提供了 WeakReference 类来实现弱引用。
虚引用:虚引用也称为幻像引用,他是最弱的一种引用。如果一个对象仅持有幻像引用,那么他就和没有任何引用一样。对其生存时间没有任何影响,我们也无法通过幻像引用来取得一个对象实例。事实上,我们可以通过为一个对象设置为幻像引用关联从而跟踪这个对象被垃圾回收的活动。在JDK1.2之后提供了 PhantomReference 类来实现弱引用。
引用队列:引用队列 ReferenceQueue 是用来配合引用工作的,最常与幻像引用一起使用,因为幻像引用的构造函数必须指定引用队列,而其他引用类型没有引用队列一样可以运行。
当某个被引用的对象被回收的时候,JVM会指向他的引用加入到引用队列的队列末尾,这相当于是一种通知机制。这个操作其实是由 ReferenceHandler 守护线程来做的,这个守护线程是在Reference静态代码块中建立并且运行的线程,所以只要Reference 这个父类被初始化,该线程就会创建和运行,他的运行方法依赖了比较多的本地(native)方法。由于ReferenceHandler 是守护线程,除非JVM进程终结,否则他会一直在后台运行。
不同引用类型的应用场景:
1)软引用的应用:断路器
2)弱引用的应用:ThreadLocal的ThreadLocalMap实现的
3)虚引用的应用:数据库连接池(数据库连接池 Connection Pool 应该具备的一个优点就是能够有效的避免连接资源泄露,同时能够对连接资源进行回收)
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 25岁的心里话
· 闲置电脑爆改个人服务器(超详细) #公网映射 #Vmware虚拟网络编辑器
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· 零经验选手,Compose 一天开发一款小游戏!
· 通过 API 将Deepseek响应流式内容输出到前端