对象的创建与内存分配

简介: 当 JVM 收到一个 new 指令时,会检查指令中的参数在常量池是否有这个符号的引用,还会检查该类是否已经被加载过了,如果没有的话则要进行一次类加载。

创建对象


JVM 收到一个 new 指令时,会检查指令中的参数在常量池是否有这个符号的引用,还会检查该类是否已经被加载过了,如果没有的话则要进行一次类加载。


接着就是分配内存了,通常有两种方式:


  • 指针碰撞


  • 空闲列表


使用指针碰撞的前提是堆内存是完全工整的,用过的内存和没用的内存各在一边每次分配的时候只需要将指针向空闲内存一方移动一段和内存大小相等区域即可。


当堆中已经使用的内存和未使用的内存互相交错时,指针碰撞的方式就行不通了,这时就需要采用空闲列表的方式。虚拟机会维护一个空闲的列表,用于记录哪些内存是可以进行分配的,分配时直接从可用内存中直接分配即可。


堆中的内存是否工整是有垃圾收集器来决定的,如果带有压缩功能的垃圾收集器就是采用指针碰撞的方式来进行内存分配的。


分配内存时也会出现并发问题:


这样可以在创建对象的时候使用 CAS 这样的乐观锁来保证。


也可以将内存分配安排在每个线程独有的空间进行,每个线程首先在堆内存中分配一小块内存,称为本地分配缓存(TLAB : Thread Local Allocation Buffer)。


分配内存时,只需要在自己的分配缓存中分配即可,由于这个内存区域是线程私有的,所以不会出现并发问题。


可以使用 -XX:+/-UseTLAB 参数来设定 JVM 是否开启 TLAB


内存分配之后需要对该对象进行设置,如对象头。对象头的一些应用可以查看 Synchronize 关键字原理


对象访问


一个对象被创建之后自然是为了使用,在 Java 中是通过栈来引用堆内存中的对象来进行操作的。


对于我们常用的 HotSpot 虚拟机来说,这样引用关系是通过直接指针来关联的。


如图:



这样的好处就是:在 Java 里进行频繁的对象访问可以提升访问速度(相对于使用句柄池来说)。


内存分配


Eden 区分配


简单的来说对象都是在堆内存中分配的,往细一点看则是优先在 Eden 区分配。

这里就涉及到堆内存的划分了,为了方便垃圾回收,JVM 将对内存分为新生代和老年代。


而新生代中又会划分为 Eden 区,from Survivor、to Survivor 区。

其中 EdenSurvivor 区的比例默认是 8:1:1,当然也支持参数调整 -XX:SurvivorRatio=8


当在 Eden 区分配内存不足时,则会发生 minorGC ,由于 Java 对象多数是朝生夕灭的特性,所以 minorGC 通常会比较频繁,效率也比较高。


当发生 minorGC 时,JVM 会根据复制算法将存活的对象拷贝到另一个未使用的 Survivor 区,如果 Survivor 区内存不足时,则会使用分配担保策略将对象移动到老年代中。


谈到 minorGC 时,就不得不提到 fullGC(majorGC) ,这是指发生在老年代的 GC ,不论是效率还是速度都比 minorGC 慢的多,回收时还会发生 stop the world 使程序发生停顿,所以应当尽量避免发生 fullGC


老年代分配


也有一些情况会导致对象直接在老年代分配,比如当分配一个大对象时(大的数组,很长的字符串),由于 Eden 区没有足够大的连续空间来分配时,会导致提前触发一次 GC,所以尽量别频繁的创建大对象。


因此 JVM 会根据一个阈值来判断大于该阈值对象直接分配到老年代,这样可以避免在新生代频繁的发生 GC


对于一些在新生代的老对象 JVM 也会根据某种机制移动到老年代中。


JVM 是根据记录对象年龄的方式来判断该对象是否应该移动到老年代,根据新生代的复制算法,当一个对象被移动到 Survivor 区之后 JVM 就给该对象的年龄记为1,每当熬过一次 minorGC 后对象的年龄就 +1 ,直到达到阈值(默认为15)就移动到老年代中。


可以使用 -XX:MaxTenuringThreshold=15 来配置这个阈值。


总结


虽说这些内容略显枯燥,但当应用发生不正常的 GC 时,可以方便更快的定位问题。


相关文章
|
6天前
|
存储 编译器 Linux
【c++】类和对象(上)(类的定义格式、访问限定符、类域、类的实例化、对象的内存大小、this指针)
本文介绍了C++中的类和对象,包括类的概念、定义格式、访问限定符、类域、对象的创建及内存大小、以及this指针。通过示例代码详细解释了类的定义、成员函数和成员变量的作用,以及如何使用访问限定符控制成员的访问权限。此外,还讨论了对象的内存分配规则和this指针的使用场景,帮助读者深入理解面向对象编程的核心概念。
18 4
|
1月前
|
缓存 算法 Java
JVM知识体系学习六:JVM垃圾是什么、GC常用垃圾清除算法、堆内存逻辑分区、栈上分配、对象何时进入老年代、有关老年代新生代的两个问题、常见的垃圾回收器、CMS
这篇文章详细介绍了Java虚拟机(JVM)中的垃圾回收机制,包括垃圾的定义、垃圾回收算法、堆内存的逻辑分区、对象的内存分配和回收过程,以及不同垃圾回收器的工作原理和参数设置。
55 4
JVM知识体系学习六:JVM垃圾是什么、GC常用垃圾清除算法、堆内存逻辑分区、栈上分配、对象何时进入老年代、有关老年代新生代的两个问题、常见的垃圾回收器、CMS
|
1月前
|
Java 测试技术 Android开发
让星星⭐月亮告诉你,强软弱虚引用类型对象在内存足够和内存不足的情况下,面对System.gc()时,被回收情况如何?
本文介绍了Java中四种引用类型(强引用、软引用、弱引用、虚引用)的特点及行为,并通过示例代码展示了在内存充足和不足情况下这些引用类型的不同表现。文中提供了详细的测试方法和步骤,帮助理解不同引用类型在垃圾回收机制中的作用。测试环境为Eclipse + JDK1.8,需配置JVM运行参数以限制内存使用。
31 2
|
1月前
|
存储 Java
JVM知识体系学习四:排序规范(happens-before原则)、对象创建过程、对象的内存中存储布局、对象的大小、对象头内容、对象如何定位、对象如何分配
这篇文章详细地介绍了Java对象的创建过程、内存布局、对象头的MarkWord、对象的定位方式以及对象的分配策略,并深入探讨了happens-before原则以确保多线程环境下的正确同步。
51 0
JVM知识体系学习四:排序规范(happens-before原则)、对象创建过程、对象的内存中存储布局、对象的大小、对象头内容、对象如何定位、对象如何分配
|
1月前
|
存储 Java
深入理解java对象的内存布局
这篇文章深入探讨了Java对象在HotSpot虚拟机中的内存布局,包括对象头、实例数据和对齐填充三个部分,以及对象头中包含的运行时数据和类型指针等详细信息。
28 0
深入理解java对象的内存布局
|
1月前
|
存储 编译器 C++
【C++】掌握C++类的六个默认成员函数:实现高效内存管理与对象操作(二)
【C++】掌握C++类的六个默认成员函数:实现高效内存管理与对象操作
|
1月前
|
算法 Java
JVM进阶调优系列(3)堆内存的对象什么时候被回收?
堆对象的生命周期是咋样的?什么时候被回收,回收前又如何流转?具体又是被如何回收?今天重点讲对象GC,看完这篇就全都明白了。
|
1月前
|
存储 编译器 C++
【C++】掌握C++类的六个默认成员函数:实现高效内存管理与对象操作(三)
【C++】掌握C++类的六个默认成员函数:实现高效内存管理与对象操作
|
1月前
|
存储 编译器 C++
【C++】掌握C++类的六个默认成员函数:实现高效内存管理与对象操作(一)
【C++】掌握C++类的六个默认成员函数:实现高效内存管理与对象操作
|
3月前
|
存储 Java 程序员
Java中对象几种类型的内存分配(JVM对象储存机制)
Java中对象几种类型的内存分配(JVM对象储存机制)
80 5
Java中对象几种类型的内存分配(JVM对象储存机制)