详解jvm对象的创建和分配
作者:萌新J 发布时间:2022-01-27 13:39:41
目录
对象的创建
创建方式
对象的内存布局
创建过程
对象的内存分配
分配方式
并发安全
代码优化
逃逸分析的不成熟性
实际的对象空间分配过程
对象的访问
句柄
直接指针
对象的创建
创建方式
1、 new 关键字直接创建。 new ObjectName()。
2、通过 Class 反射对象的 newInstance() 方法。ObjectName obj = ObjectName.class.newInstance()。
3、通过 Class 反射对象获取 Constructor 类,再调用其 newInstance() 方法。 ObjectName obj = ObjectName.class.getConstructor.newInstance()。
4、在类实现 Cloneable 接口的前提下,使用对象的 clone() 方法。ObjectName obj = obj.clone()。(如果内部有自定义类属性,并且想要实现深克隆(新创建的对象和原有的对象不是同一个),那么就需要让该属性类也实现 Cloneable 接口。
5、使用反序列化。(为了避免属性丢失,需要让类实现 Serializable 接口)
public static void main(String[] args){
try {
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(FilePath))
ObjectName obj = ois.readObject();
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
对象的内存布局
在对象身上,存储了关于这个对象的所有信息。
创建过程
1、根据创建对象的信息去内存中存放类信息的常量池中寻找是否存在要加载的类信息,如果存在直接创建对象;如果不存在就先进行该类的加载。
2、为对象分配空间。这里涉及到线程位置分配的安全和效率,比较复杂,会在下面详细来说。
3、初始化分配到对应的位置。
4、设置对象的对象头。
5、执行 init 方法(执行非静态代理块和实例属性的初始化以及执行实例构造方法)
对象的内存分配
分配方式
1、指针碰撞:如果 Java 堆内存是规整的,也就是对象的创建位置都是紧挨着的,这样的话直接将指针指示器向空闲方向移动要创建对象大小的距离就可以了。
2、空闲列表:如果 Java 堆内存是不规整的,那么就需要维护一个空闲列表来记录哪些位置是空闲的以及多大。在分配时就在列表上查询,找到合适的位置分配。
并发安全
由于在堆的线程共享的,所以对象的创建分配的空间可能同时也是另外一个线程对象创建的分配位置,这就导致了并发问题,所以为了保证对象创建的并发安全,可以有下面两种方式:
1、在分配空间时进行同步处理(采用 CAS +回旋锁的方式来保证)
2、TLAB:新的线程创建时会在堆中划分一块区域给该线程,后面该线程创建的对象都会在该位置存放,当空间不足时才使用第一种方式。(HotSpot 使用)。
代码优化
1、栈上分配。通过逃逸分析判断创建的对象是否逃逸出方法(也就是这个对象是否在当前方法的外部被调用),如果没有逃逸出方法,那么就有可能直接在栈上分类空间来保存。
2、同步省略。JIT 在编译时会判断同步块所使用的锁对象是否只能被一个线程访问而没有被发布到其他的线程。如果没有,那么 JIT 编译器在编译这段代码时就会取消这段代码的同步。
3、分离对象(标量替换)。有的对象可能不需要作为一个连续的内存结构存在也可以被访问到,那么对象的部分(或全部)可以不存储在内存,而是存储在栈中。
标量:无法再被分解的数据。如一个类的基本数据类型属性。
聚合量:还可以被分解的数据。如一个类的自定义属性。
逃逸分析的不成熟性
关于逃逸分析目前还是处于不稳定的阶段,因为无法保证逃逸分析的性能消耗一定高于其节省的性能。简单来说就是可能执行了逃逸分析,结果发现都是逃逸出方法的对象,这样逃逸分析并没有提高性能,同时执行逃逸分析也消耗了一定的性能,造成得不偿失。所以,逃逸分析在 JVM 中没有实现 栈上分配的功能的,但是其还是在 JIT 中起到了优化作用。所以可以说对象都是创建在堆上的。而我们一般所说的对象创建在栈上,实际情况是因为标量替换的作用。
实际的对象空间分配过程
首先会判断是否可以进行标量替换,如果可以直接使用标量替换,然后结束。不可以的话再尝试在当前线程划分的区域创建,如果区域不够再尝试使用 CAS+ 自旋锁在其他位置划分,失败就再次尝试,直到成功。
对象的访问
Java 程序通过栈上的引用访问堆中的对象。对象的访问方式取决于 JVM 虚拟机上的实现,目前主流的访问方式是句柄和直接指针。
句柄
句柄相当于一个中间表,存储着对应实例对象的地址以及实例数据所对应类信息的地址。
优势:比较稳定,当对象被移动后(垃圾回收时移动对象是非常常见的事)时只需要改变句柄中的指针就可以了。句柄本身不需要改变。
直接指针
引用直接指向实例对象,在对象上保存对应的类信息所在的地址。
优势:查找快,在栈上的引用可以很快找到对应的对象。这也是 HotSpot 默认的访问方式。
来源:https://www.cnblogs.com/mengxinJ/p/14496285.html


猜你喜欢
- 下面给大家介绍C#使用ICSharpCode.SharpZipLib.dll进行文件的压缩与解压功能,具体代码如下所示:using Syst
- 如何获取二维数组中的元素个数呢?int[,] array = new int[,] {{1,2,3},{4,5,6},{7,8,9}};//
- java文件打包jar运行有效步骤:1.cmd 到当前目录(默认包主类所在目录为例) set classpath = 默认包主类所在目录2.
- 本文实例为大家分享了Java实现考试系统的具体代码,供大家参考,具体内容如下说明这里的考试系统是指由学生,老师以及考试机构成的,学生通过用户
- 本文实例为大家分享了java实现转圈打印矩阵的具体代码,供大家参考,具体内容如下给定一个整形矩阵Matrix,请按照顺时针方向转圈的方式,输
- 利用闲余时间想自己搭建一个springboo
- windows环境下1、输出logcat日志到本地文件adb logcat -> F:/logcat.txt2、输出带时间的logca
- 本文实例讲述了C#中的try catch finally用法。分享给大家供大家参考。具体分析如下:try中的程序块是有可能发生错误的程序块,
- 效果:一个手指实现(所有手势事件)和(部分事件的);A. 所有手势activity_main.xml<TextView android
- 关于静态类型检查和动态类型检查的解释:静态类型检查:基于程序的源代码来验证类型安全的过程;动态类型检查:在程序运行期间验证类型安全的过程;J
- spring.activemq.pool.enabled=false时,每发送一条数据都需要创建一个连接,这样会出现频繁创建和销毁连接的场景
- 本文实例讲述了Android控件Tween动画(补间动画)实现方法。分享给大家供大家参考,具体如下:Android动画中的Tween动画:是
- 概述JDK的bin目录下提供了很多命令工具,比如java.exe,javap.exe,javac.exe。。。。。。这些命令由jdk/lib
- 动态数组ArrayList类在System.Collecions的命名空间下,所以使用时要加入System.Collecions命名空间,而
- 1、原来是将EditView放到了popupwindow,发现EditView原有的复制、粘贴、全选、选择功能失效了,所以便用DialogF
- Android 中ScrollView嵌套GridView,ListView的实例在Android开发中,经常有一些UI需要进行固定styl
- 本文通俗易懂的分析了C#中值类型和引用类型的区别。分享给大家供大家参考。具体分析如下:似乎“值类型和引用类型的区别”是今年面试的流行趋势,我
- Comparable 比较器,内置定义的比较方法,实现比较 较简单Comparator 策略模式,需要定义不同的策略和比较的对象,实现比较
- 需要的jar包:数据库代码:create database school character set utf8;use school;CRE
- 本文实例为大家分享了unity通过Mesh网格绘制球体的具体代码,供大家参考,具体内容如下接着上一篇文章说:球体public class 球