浅谈Java对象的生与死

2022-10-01  本文已影响0人  王侦

Java对象在JVM中如何分配?分配在哪个地方?Java有自动内存管理机制,那它是怎么判定一个对象是垃圾对象的?以及怎样对垃圾对象回收?

这篇文章就聊聊这些关于Java对象生与死相关的事。

一、模板 — 类加载

1.1 类加载过程

当JVM创建一个对象时,首先会判断该对象的类是否已加载,如果没有加载,则进行类加载。类加载会将Class文件(或者流)加载到内存,对数据进行校验、转换解析和初始化,最终形成可以被虚拟机直接使用的Java类型。

类加载主要包括五个步骤:

1.2 双亲委派模型

类加载器加载类,一般情况下,都会遵循双亲委派模型。那么啥是双亲委派模型?为什么要设计双亲委派模型?

双亲委派模型指的是:类加载器之间存在父子关系,但是这种父子关系一般不是以继承(Inheritance)的关系来实现的,而是通常使用组合(Composition)关系来复用父加载器的代码。如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器去完成,每一个层次的类加载器都是如此,因此所有的加载请求最终都应该传送到最顶层的启动类加载器中,只有当父加载器反馈自己无法完成这个加载请求(它的搜索范围中没有找到所需的类)时,子加载器才会尝试自己去完成加载。

设计双亲委派模型有两个原因:

这样能够保证:

1.3 Tomcat打破双亲委派机制

双亲委派模型能够适用于所有应用场景吗?显然不行。

在Tomcat里,一个web容器可能需要部署两个应用程序,不同的应用程序可能会依赖同一个第三方类库的不同版本,不能要求同一个类库在同一个服务器只有一份,因此要保证每个应用程序的类库都是独立的,保证相互隔离。另外,web容器要支持jsp的修改,我们知道,jsp 文件最终也是要编译成class文件才能在虚拟机中运行,但程序运行后修改jsp已经是司空见惯的事情, web容器需要支持 jsp 修改后不用重启。

这些功能显然是双亲委派机制所不支持的,为了支持这些功能,Tomcat设计了自己的类加载器体系。

WebappClassLoader:各个Webapp私有的类加载器,加载路径中的class只对当前Webapp可见,比如加载war包里相关的类,每个war包应用都有自己的WebappClassLoader,实现相互隔离,比如不同war包应用引入了不同的spring版本,这样实现就能加载各自的spring版本。

JasperLoader的加载范围仅仅是这个JSP文件所编译出来的那一个.Class文件,它出现的目的就是为了被丢弃:当Web容器检测到JSP文件被修改时,会替换掉目前的JasperLoader的实例,并通过再建立一个新的Jsp类加载器来实现JSP文件的热加载功能。

这里着重讲讲Tomcat 的自定义类加载器 WebAppClassLoader是怎么打破双亲委派机制,实现多版本共存的。核心是重写 ClassLoader 的两个方法:findClass 和 loadClass。

先来看看loadClass:

再来看看findClass:

二、分配内存

有了模板,就可以创建对象了。那在哪里创建对象呢?所以首先就要解决内存分配的问题。

2.1 栈上分配

为了减少临时对象在堆内分配的数量,JVM通过逃逸分析确定该对象不会被外部访问。如果不会逃逸可以将该对象在栈上分配内存。栈上分配依赖于逃逸分析和标量替换。

通过逃逸分析确定该对象不会被外部访问,并且对象可以被进一步分解时,JVM不会创建该对象,而是将该对象成员变量分解若干个被这个方法使用的成员变量所代替,这些代替的成员变量在栈帧或寄存器上分配空间,这样就不会因为没有一大块连续空间导致对象内存不够分配。

2.2 对象优先在Eden分配

分配内存有两种方式:

分配内存必然会遇到线程安全的问题,怎么解决?

2.3 大对象直接进入老年代

为了避免为大对象分配内存时的复制操作而降低效率。

三、对象数据填充

3.1 赋0值

内存分配完成后,虚拟机需要将分配到的内存空间都初始化为零值(不包括对象头), 如果使用TLAB,这一工作过程也可以提前至TLAB分配时进行。这一步操作保证了对象的实例字段在Java代码中可以不赋初始值就直接使用,程序能访问到这些字段的数据类型所对应的零值。

3.2 对象头设置

对象整体而言包括三部分:对象头、实例数据、对其填充。

对象头主要包括两部分(数组还有第三部分:数组长度)

这里为了节省空间,64位JVM默认开启了指针压缩。可以通过对对象指针的存入堆内存时压缩编码、取出到cpu寄存器后解码方式进行优化(对象指针在堆中是32位,在寄存器中是35位,2的35次方=32G),使得jvm只用32位地址就可以支持更大的内存配置(小于等于32G)。

四、对象的使用

对象一般都是在某个方法中被使用,其实就是Execution engine(执行引擎)执行classes中指令时使用。

当使用该对象的方法调用完成后,该方法的栈帧就会被销毁,那么就有可能导致该对象称为垃圾对象。

五、垃圾对象的判定与回收

5.1 对象已死?

怎样判定对象是垃圾对象,一般有两种方法,JVM使用可达性分析算法。

5.2 垃圾收集算法

主要有三种垃圾收集算法:

上一篇 下一篇

猜你喜欢

热点阅读