JVM知识
JVM内存模子
JVM的内存模子先容一下
根据 JVM8 规范,JVM 运行时内存共分为假造机栈、堆、方法区、程序计数器、本地方法栈五个部分。
https://img-home.csdnimg.cn/images/20230724024159.png?origin_url=file%3A%2F%2F%2FC%3A%5CUsers%5C%E5%B0%8F%E5%8F%AF%E7%88%B1~1%5CAppData%5CLocal%5CTemp%5Cksohtml24804%5Cwps5.jpg&pos_id=Ywmfk4gf
JVM的内存布局重要分为以下几个部分:
方法区:存放类对象
Java 假造机栈:存放方法之间的调用关系(方法的入口,方法的形参,方法的返回值,局部变量)
本地方法栈:与假造机栈雷同,区别是假造机栈执行java方法,本地方法站执行native方法。
程序计数器:存放下一条执行的指令存放的地址
堆内存:全部的对象和数组都在堆上举行分配。
JVM内存模子里的堆和栈有什么区别?
用途:栈重要用于存储局部变量、方法调用的参数、方法返回地址以及一些临时数据。
。堆用于存储对象的实例(包括类的实例和数组)。
生命周期:
栈中的数据具有确定的生命周期,当一个方法调用结束时,其对应的栈帧就会被销毁,栈中存储的局部变量也会随之消散。
堆中的对象生命周期不确定,对象会在垃圾回收机制(Garbage Collection, GC)检测到对象不再被引用时才被回收。
存取速度:
栈的存取速度通常比堆快,由于栈遵循先辈后出(LIFO, Last In First Out)的原则,操作简单快速。
堆的存取速度相对较慢,由于对象在堆上的分配和回收需要更多的时间,而且垃圾回收机制的运行也会影响性能。
存储空间:
栈的空间相对较小,且固定,由操作系统管理。当栈溢出时,通常是由于递归过深或局部变量过大。
堆的空间较大,动态扩展,由JVM管理。堆溢出通常是由于创建了太多的大对象或未能及时回收不再使用的对象。
栈中存的到底是指针还是对象?
栈中存储的不是对象,而是对象的引用。也就是说,当你在方法中声明一个对象,比如MyObject obj = new MyObject();,这里的obj实际上是一个存储在栈上的引用,指向堆中实际的对象实例。这个引用是一个固定大小的数据(例如在64位系统上是8字节),它指向堆中分配给对象的内存区域。
堆分为哪几部分呢?
Java堆(Heap)是Java假造机(JVM)中内存管理的一个重要区域,重要用于存放对象实例和数组。随着JVM的发展和不同垃圾收集器的实现,堆的具体分别可能会有所不同,但通常可以分为以下几个部分:
新生代(Young Generation):新生代分为Eden Space和Survivor Space。在Eden Space中, 大多数新创建的对象起首存放在这里。Eden区相对较小,当Eden区满时,会触发一次Minor GC(新生代垃圾回收)。在Survivor Spaces中,通常分为两个相等大小的区域,称为S0(Survivor 0)和S1(Survivor 1)。在每次Minor GC后,存活下来的对象会被移动到其中一个Survivor空间,以继续它们的生命周期。这两个区域轮番充当对象的中转站,帮助区分短暂存活的对象和恒久存活的对象。
老年代(Old Generation/Tenured Generation):存放过一次或多次Minor GC仍存活的对象会被移动到老年代。老年代中的对象生命周期较长,因此Major GC(也称为Full GC,涉及老年代的垃圾回收)发生的频率相对较低,但其执行时间通常比Minor GC长。老年代的空间通常比新生代大,以存储更多的恒久存活对象。
方法区 :从Java 8开始,永久代(Permanent Generation)被元空间取代,用于存储类的元数据信息,如类的布局信息(如字段、方法信息等)。元空间并不在Java堆中,而是使用本地内存,这办理了永久代容易出现的内存溢出问题。
大对象区(Large Object Space / Humongous Objects):在某些JVM实现中(如G1垃圾收集器),为大对象分配了专门的区域,称为大对象区或Humongous Objects区域。大对象是指需要大量一连内存空间的对象,如大数组。这类对象直接分配在老年代,以避免因频繁的年轻代提升而导致的内存碎片化问题。
程序计数器的作用,为什么是私有的?
我们思量一下,假如在线程切换的过程中,下一条指令执行到哪里了,是不是还是会用到我们的程序计数器啊。每个线程都有自己的程序计数器,由于它们各自执行的代码的指令地址是不一样的呀,所以每个线程都应该有自己的程序计数器。
方法区中的方法的执行过程?
当程序中通过对象或类直接调用某个方法时,重要包括以下几个步骤:
分析方法调用:JVM会根据方法的符号引用找到实际的方法地址(假如之前没有分析过的话)。
栈帧创建:在调用一个方法前,JVM会在当前线程的Java假造机栈中为该方法分配一个新的栈帧,用于存储局部变量表、操作数栈、动态链接、方法出口等信息。
执行方法:执行方法内的字节码指令,涉及的操作可能包括局部变量的读写、操作数栈的操作、跳转控制、对象创建、方法调用等。
返回处理:方法执行完毕后,可能会返回一个结果给调用者,并清理当前栈帧,恢复调用者的执行环境。
方法区中还有哪些东西?
类信息:包括类的布局信息、类的访问修饰符、父类与接口等信息。
常量池:存储类和接口中的常量,包括字面值常量、符号引用,以及运行时常量池。
静态变量:存储类的静态变量,这些变量在类初始化的时候被赋值。
方法字节码:存储类的方法字节码,即编译后的代码。
符号引用:存储类和方法的符号引用,是一种直接引用不同于直接引用的引用范例。
运行时常量池:存储着在类文件中的常量池数据,在类加载后在方法区天生该运行时常量池。
常量池缓存:用于提升类加载的服从,将常用的常量缓存起来方便使用。
String保存在哪里呢?
String 保存在字符串常量池中,不同于其他对象,它的值是不可变的,且可以被多个引用共享。
String s = new String(“abc”)执行过程中分别对应哪些内存区域?
起首,我们看到这个代码中有一个new关键字,我们知道new指令是创建一个类的实例对象并完成加载初始化的,因此这个字符串对象是在运行期才能确定的,创建的字符串对象是在堆内存上。
其次,在String的构造方法中传递了一个字符串abc,由于这里的abc是被final修饰的属性,所以它是一个字符串常量。在首次构建这个对象时,JVM拿字面量"abc"去字符串常量池试图获取其对应String对象的引用。于是在堆中创建了一个"abc"的String对象,并将其引用保存到字符串常量池中,然后返回;
所以,假如abc这个字符串常量不存在,则创建两个对象,分别是abc这个字符串常量,以及new String这个实例对象。假如abc这字符串常量存在,则只会创建一个对象。
引用范例有哪些?有什么区别?
引用范例重要分为强软弱虚四种:
强引用指的就是代码中普遍存在的赋值方式,比如A a = new A()这种。强引用关联的对象,永久不会被GC回收。
弱引用可以用WeakReference来描述,他的强度比软引用更低一点,弱引用的对象下一次GC的时候肯定会被回收,而不管内存是否富足。
软引用可以用SoftReference来描述,指的是那些有用但是不是必须要的对象。系统在发生内存溢出前会对这类引用的对象举行回收。
虚引用也被称作幻影引用,是最弱的引用关系,可以用PhantomReference来描述,他必须和ReferenceQueue一起使用,同样的当发生GC的时候,虚引用也会被回收。可以用虚引用来管理堆外内存。
弱引用了解吗?举例阐明在哪里可以用?
弱引用的使用场景:
缓存系统:弱引用常用于实现缓存,特别是当盼望缓存项可以大概在内存压力下主动开释时。假如缓存的大小不受控制,可能会导致内存溢出。使用弱引用来维护缓存,可以让JVM在需要更多内存时主动清理这些缓存对象。
对象池:在对象池中,弱引用可以用来管理那些临时不使用的对象。当对象不再被强引用时,它们可以被垃圾回收,开释内存。
内存走漏和内存溢出的理解?
内存泄露:内存走漏是指程序在运行过程中不再使用的对象仍然被引用,而无法被垃圾收集器回收,从而导致可用内存逐渐减少。
内存泄露常见缘故原由:
静态集合:使用静态数据布局(如HashMap或ArrayList)存储对象,且未清理。
线程:未停止的线程可能持有对象引用,无法被回收。
内存溢出:内存溢出是指Java假造机(JVM)在申请内存时,无法找到富足的内存,最终引发OutOfMemoryError。这通常发生在堆内存不足以存放新创建的对象时。
内存溢出常见缘故原由:
大量对象创建:程序中不断创建大量对象,超出JVM堆的限制。
递归调用:深度递归导致栈溢出。
jvm 内存布局有哪几种内存溢出的环境?
堆内存溢出:当出现java.lang.OutOfMemoryError:Java heap space异常时,就是堆内存溢出了。缘故原由是代码中可能存在大对象分配,或者发生了内存泄露,导致在多次GC之后,还是无法找到一块富足大的内存容纳当前对象。
栈溢出:假如我们写一段程序不断的举行递归调用,而且没有退出条件,就会导致不断地举行压栈。雷同这种环境,JVM 实际会抛出 StackOverFlowError;固然,假如 JVM 试图去扩展栈空间的的时候失败,则会抛出 OutOfMemoryError。
类初始化和类加载
创建对象的过程?
在Java中创建对象的过程包括以下几个步骤:
类加载查抄:假造机遇到一条 new 指令时,起首将去查抄这个指令的参数是否能在常量池中定位到一个类的符号引用,并且查抄这个符号引用代表的类是否已被加载过、分析和初始化过。假如没有,那必须先执行相应的类加载过程。
分配内存:在类加载查抄通事后,接下来假造机将为新生对象分配内存。对象所需的内存大小在类加载完成后便可确定,为对象分配空间的使命等同于把一块确定大小的内存从 Java 堆中分别出来。
初始化零值:内存分配完成后,假造机需要将分配到的内存空间都初始化为零值(不包括对象头),这一步操作保证了对象的实例字段在 Java 代码中可以不赋初始值就直接使用,程序能访问到这些字段的数据范例所对应的零值。
举行须要设置,比如对象头:初始化零值完成之后,假造机要对对象举行须要的设置,例如这个对象是哪个类的实例、如何才能找到类的元数据信息、对象的哈希码、对象的 GC 分代年龄等信息。这些信息存放在对象头中。另外,根据假造机当前运行状态的不同,如是否启用偏向锁等,对象头会有不同的设置方式。
执行 init 方法:在上面工作都完成之后,从假造机的视角来看,一个新的对象已经产生了,但从 Java 程序的视角来看,对象创建才刚开始——构造函数,即class文件中的方法还没有执行,全部的字段都还为零,对象需要的其他资源和状态信息还没有按照预定的意图构造好。所以一样寻常来说,执行 new 指令之后会接着执行方法,把对象按照程序员的意愿举行初始化,如许一个真正可用的对象才算完全被构造出来。
对象的生命周期
对象的生命周期包括创建、使用和销毁三个阶段:
创建:对象通过关键字new在堆内存中被实例化,构造函数被调用,对象的内存空间被分配。
使用:对象被引用并执行相应的操作,可以通过引用访问对象的属性和方法,在程序运行过程中被不断使用。
销毁:当对象不再被引用时,通过垃圾回收机制主动回收对象所占用的内存空间。垃圾回收器会在得当的时候检测并回收不再被引用的对象,开释对象占用的内存空间,完成对象的销毁过程。
类加载器有哪些?
爷
启动类加载器(Bootstrap Class Loader):这是最顶层的类加载器,负责加载Java的核心库
父
扩展类加载器(Extension Class Loader):它是Java语言实现的,继承自ClassLoader类,负责加载扩展库目次,扩展类加载器由启动类加载器加载,并且父加载器就是启动类加载器。
子
应用程序类加载器(Application Class Loader):负责加载当前项目目次和第三方库目次。
这些类加载器之间的关系形成了双亲委派模子,其核心思想是当一个类加载器收到类加载的哀求时,起首不会自己去实验加载这个类,而是把这个哀求委派给父类加载器去完成,每一层次的类加载器都是云云,因此全部的加载哀求最终都应该传送到顶层的启动类加载器中。
只有当父加载器反馈自己无法完成这个加载哀求(它的搜刮范围中没有找到所需的类)时,子加载器才会实验自己去加载。
双亲委派模子的作用
保证类的唯一性:通过委托机制,确保了全部加载哀求都会传递到启动类加载器,避免了不同类加载器重复加载雷同类的环境,保证了Java核心类库的统一性,也防止了用户自界说类覆盖核心类库的可能。
保证安全性:由于Java核心库被启动类加载器加载,而启动类加载器只加载信任的类路径中的类,如允许以防止不可信的类假冒核心类,增强了系统的安全性。例如,恶意代码无法自界说一个java.lang.System类并加载到JVM中,由于这个哀求会被委托给启动类加载器,而启动类加载器只会加载标准的Java库中的类。
简化了加载流程:通过委派,大部分类可以大概被精确的类加载器加载,减少了每个加载器需要处理的类的数目,简化了类的加载过程,提高了加载服从。
讲一下类加载过程?
加载:找到.class文件,打开文件,读取文件内容
验证:确保class文件中的字节省包含的信息,符合当前假造机的要求
准备:给类对象分配空间
分析:针对类对象中的字符串常量举行处理,举行一些初始化操作
初始化:针对类对象举行初始化
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
页:
[1]