异常将上下文初始化事件发送到类的侦听器实例_深入浅出JVM性能调优——JVM内存(11)


3、线程上下文类加载器
线程上下文类加载器可通过 java.lang. 中的方法 r() 获得,可以通过 r( cl) 来设置线程的上下文类加载器 。如果没有通过 r( cl) 方法进行设置的话,线程将继承其父线程的上下文类加载器 。Java 应用运行的初始线程的上下文类加载器是应用程序类加载器 。在线程中运行的代码可以通过此类加载器来加载类和资源 。线程上线文类加载器使得父类加载器可以去请求子类加载器完成类加载的行为,这在一定程度上是违背了双亲委派模型的原则 。
六、对象及其生命周期 1、实例化对象
1)实例化一个类有四种途径:
2)实例化对象的过程:
2、对象的内存布局
在虚拟机里,对象在堆内存中的存储布局可以划分为三个部分:对象头()、实例数据( Data)和对齐填充() 。
1)对象头:
对象头主要由两部分组成:Mark Word 和类型指针,如果是数组对象,还会包含一个数组长度 。
这三部分数据的长度在32位和64位的虚拟机(未开启压缩指针)中分别为32个比特和64个比特 。64 位虚拟机中,为了节约内存可以使用选项 + 开启指针压缩,某些数据会由 64位压缩至32位 。
2)实例数据:
实例数据部分是对象真正存储的有效信息,即对象的各个字段数据,无论是从父类继承下来的,还是在子类中定义的字段都必须记录起来 。
3)对齐填充:
对齐填充仅仅起着占位符的作用,由于虚拟机的自动内存管理系统要求对象起始地址必须是8字节的整数倍,就是任何对象的大小都必须是8字节的整数倍 。对象头部分已经被设计成正好是8字节的倍数,因此,如果对象实例数据部分没有对齐的话,就需要通过对齐填充来补全 。
4)计算对象占用内存大小:
从上面的内容可以看出,一个对象对内存的占用主要分两部分:对象头和实例数据 。在64位机器上,对象头中的 Mark Word 和类型指针各占 64 比特,就是16字节 。实例数据部分,可以根据类型来判断,如 int 占 4 个字节,long 占 8 个字节,字符串中文占3个字节、数字或字母占1个字节来计算,就大概能计算出一个对象占用的内存大小 。当然,如果是数组、Map、List 之类的对象,就会占用更多的内存 。
3、对象访问定位
创建对象后,这个引用变量会压入栈中,即一个 ,它是一个指向对象的引用,这个引用定位的方式主要有两种:使用句柄访问对象和直接指针访问对象 。
1)通过句柄访问对象:
使用句柄访问的话,Java堆中将可能会划分出一块内存来作为句柄池,中存储的就是对象的句柄地址,而句柄中包含了对象实例数据与类型数据各自具体的地址信息 。
使用句柄来访问的最大好处就是中存储的是稳定句柄地址,在对象被移动(垃圾收集时移动对象)时只会改变句柄中的实例数据指针,而本身不需要被修改 。
2)通过直接指针访问对象:
如果使用直接指针访问的话,Java堆中对象的内存布局就必须放置访问类型数据的相关信息(Mark Word 中记录了类型指针), 中存储的直接就是对象地址,如果只是访问对象本身的话,就不需要多一次间接访问的开销 。
使用直接指针来访问最大的好处就是速度更快,它节省了一次指针定位的时间开销, 虚拟机主要就是使用这种方式进行对象访问 。
4、垃圾收集
当对象不再被程序所引用时,它所使用的堆空间就需要被回收,以便被后续的新对象所使用 。JVM 的内存分配管理机制会自动帮我们回收无用的对象,它知道如何确定对象不再被引用,什么时候去回收这些垃圾对象,使用什么回收策略来回收更高效,以及如何管理内存,这部分就是JVM的垃圾收集相关的内容了 。