对象的创建与内存分配

标签: Java 进阶 | 发表时间:2018-01-18 02:01 | 作者:
出处:http://crossoverjie.top/

创建对象

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 ,不论是效率还是速度都比 majorGC 慢的多,回收时还会发生 stop the world 使程序发生停顿,所以应当尽量避免发生 fullGC

老年代分配

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

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

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

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

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

总结

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

号外

最近在总结一些 Java 相关的知识点,感兴趣的朋友可以一起维护。

地址: https://github.com/crossoverJie/Java-Interview

相关 [对象 内存] 推荐:

对象的创建与内存分配

- - crossoverJie's Blog
当 JVM 收到一个 new 指令时,会检查指令中的参数在常量池是否有这个符号的引用,还会检查该类是否已经被 加载过了,如果没有的话则要进行一次类加载. 接着就是分配内存了,通常有两种方式:. 使用指针碰撞的前提是堆内存是 完全工整的,用过的内存和没用的内存各在一边每次分配的时候只需要将指针向空闲内存一方移动一段和内存大小相等区域即可.

多重继承及虚继承中对象内存的分布

- Michael - 淘宝数据平台与产品部官方博客 tbdata.org
这篇文章主要讲解G++编译器中虚继承的对象内存分布问题,从中也引出了dynamic_cast和static_cast本质区别、虚函数表的格式等一些大部分C++程序员都似是而非的概念. 问题拿捏得十分到位,下面是我对原文的翻译,原文见这里(By Edsko de Vries, January 2006).

避免DOM对象内存泄漏的使用准则

- johnny - W3C标准WEB前端DHTML精英俱乐部
终于,整理了一下在WEB前端脚本开发时因DOM对象造成的内存泄漏原因及解决方案. 避免DOM对象内存泄漏的使用准则. 取自DOM元素的DOM对象,添加属性时,属性值不能是与该DOM对象同作用域下声明的函数,如:a.b=function(){};". 如果该DOM对象被其他对象(如:b)引用,该DOM对象的属性值不能包含有对象b,如:b.push(a);a.b=b;.

获得Java对象内存占用大小

- - BlogJava-首页技术区
懒惰,直接上代码,用法见JAVA DOC. stone2083 2013-06-08 17:32 发表评论.

一个Java对象到底占用多大内存? - zhanjindong

- - 博客园_首页
最近在读《深入理解Java虚拟机》,对Java对象的内存布局有了进一步的认识,于是脑子里自然而然就有一个很普通的问题,就是一个Java对象到底占用多大内存. 在网上搜到了一篇博客讲的非常好: http://yueyemaitian.iteye.com/blog/2033046,里面提供的这个类也非常实用:.

Java程序计算各种对象所占内存的大小的方法

- - Java - 编程语言 - ITeye博客
System.out.println("--- Memory Usage:"); /*打印一行字符串---Memory Usage*/. Runtime rt=Runtime.getRuntime( ); //获得系统的Runtime对象rt. System.out.println("Total Memory= " + rt.totalMemory( )+//打印总内存大小.

JAVA内存使用--如何计算一个Java对象占用的字节数

- - Java - 编程语言 - ITeye博客
转自(http://blog.csdn.net/kp034/article/details/7077757). 通常,我们谈论的堆内存使用的前提是以“一般情况”为背景的. 1.某些情况下,JVM根本就没有把Object放入堆中. 例如:原则上讲,一个小的thread-local对象存在于栈中,而不是在堆中.

javascript对象转json

- - JavaScript - Web前端 - ITeye博客
把javascript对象转成json. 已有 0 人发表留言,猛击->> 这里<<-参与讨论. —软件人才免语言低担保 赴美带薪读研.

MongoDB与内存

- 高春辉 - 火丁笔记
但凡初次接触MongoDB的人,无不惊讶于它对内存的贪得无厌,至于个中缘由,我先讲讲Linux是如何管理内存的,再说说MongoDB是如何使用内存的,答案自然就清楚了. 据说带着问题学习更有效,那就先看一个MongoDB服务器的top命令结果:. 这台MongoDB服务器有没有性能问题. 先讲讲Linux是如何管理内存的.