JVM专题

一、前言

jvm有着自动内存管理机制,所以不容易出现内存泄漏或内存溢出的问题。但是一旦出现了,就需要对JVM足够了解,才能更好的排查错误。

二、java内存区域详解

运行时数据区

JDK1.8之前:

JDK1.8:

 

线程私有:虚拟机栈、本地方法栈、程序计数器

线程公有:堆、方法区、直接内存(非运行时数据区的一部分)

虚拟机栈:生命周期随着线程的创建而创建,消亡而消亡。其是由一个个栈帧构成的,也就是一个个方法。栈帧里边主要有局部变量表、操作数栈、动态链接、方法返回。 主要就是进栈和出栈的过程,执行方法就进栈,执行结束出栈。

局部变量表主要是存放了方法的形参和局部变量等。局部变量表中的变量只在当前方法调用中有效。当方法调用结束后,随着栈帧的销毁,局部变量表也会销毁。

操作数栈主要用于保存一些计算过程中的中间结果,同时作为计算过程中变量临时的存储空间。

举例所示:

 

 动态链接主要服务于一个方法调用其他方法的场景。一个Java源文件编译成字节码文件后,所有的变量和方法的引用都作为符号引用保存在了Class文件的常量池中,当一个方法调用另一个方法的时候,就会将方法的符号引用转化为直接引用(也就是内存地址)。动态链接的作用就是将方法的符号引用转换为直接引用。

方法返回地址主要有两种方式:一种是return 一种是异常返回

常见面试题
举例说明栈溢出的情况?
当线程申请的栈的数量超过了虚拟机栈允许的最大深度,会出现StackOverFlowError栈溢出的异常。
调整栈的大小,就能保证不出现溢出吗?
调整栈的大小并不能保证栈不溢出。只是可能会增大当前线程申请的栈的数量。
分配的栈内存越大越好吗?
分配栈的内存越大的话,会挤压其他部分的内存大小,并不是分配的栈大小越大越好,需要按需调整。
垃圾回收是否会涉及到虚拟机栈?
虚拟机栈只有出栈和入栈的操作,不存在垃圾回收的情况,栈中只会存在StackOverFlowError的异常,在我们常用的HotSpot虚拟机中,栈是不允许动态扩展的,所以也不涉及OutOfMemoryError。
方法中定义的局部变量是否线程安全?
不一定,这边要看是否有多个线程操作局部变量,对应线程独占的变量是线程安全的,线程共享的变量就需要进行同步机制保证线程安全的问题。
本地方法栈其和虚拟机栈类似,只不过虚拟机栈执行的是Java方法,本地方法栈执行的是本地方法。

堆:里边存放的是实例对象,几乎所有的实例对象和数组都在这里分配内存。从 JDK 1.7 开始已经默认开启逃逸分析,如果某些方法中的对象引用没有被返回或者未被外面使用(也就是未逃逸出去),那么对象可以直接在栈上分配内存。

方法区:会存储类信息、静态变量、常量、方法信息、字段信息以及即时编译器编译后的代码缓存等数据。

运行时常量池:存放编译期生成的字面量和符号引用。

直接内存:是堆外内存,这些内存直接受操作系统管理。好处是:减少垃圾回收,提高复制效率。

1、减少了垃圾回收

使用堆外内存的话,堆外内存是直接受操作系统管理( 而不是虚拟机 )。这样做的结果就是能保持一个较小的堆内内存,以减少垃圾收集对应用的影响。

2、提升复制速度(io效率)

堆内内存由JVM管理,属于“用户态”;而堆外内存由OS管理,属于“内核态”。如果从堆内向磁盘写数据时,数据会被先复制到堆外内存,即内核缓冲区,然后再由OS写入磁盘,使用堆外内存避免了这个操作。

直接内存的读写操作比普通Buffer快,但它的创建、销毁比普通Buffer慢。

三、对象的创建过程

1.类加载检查

当虚拟机遇到一条new指令,就要去常量池中找有没有对应类的符号引用,并检查这个符号引用代表的类是否被解析加载初始化过,如果没有需要加载。

2.分配内存地址        

类加载对象的大小就确定了,现在就是将确定大小的内存空间从堆中分出来。有两种方式:指针碰撞和空闲列表。根据垃圾回收方式看堆是否规整,来看使用那种方式。

指针碰撞:就是指针用于区分已经用过的内存和没有用过的,等分配的时候指针朝着没用过的内存方向移动对象内存大小位置即可。

空闲列表;由一个列表维护,会记录哪些内存可用。

3.初始化零值

为实例字段赋值,保证不赋值就可以使用。

4.设置对象头

初始化零值完成之后,虚拟机要对对象进行必要的设置,例如这个对象是哪个类的实例、如何才能找到类的元数据信息、对象的哈希码、对象的 GC 分代年龄等信息。 这些信息存放在对象头中。 另外,根据虚拟机当前运行状态的不同,如是否启用偏向锁等,对象头会有不同的设置方式。

5.执行init方法

在上面工作都完成之后,从虚拟机的视角来看,一个新的对象已经产生了,但从 Java 程序的视角来看,对象创建才刚开始,<init> 方法还没有执行,所有的字段都还为零。所以一般来说,执行 new 指令之后会接着执行 <init> 方法,把对象按照程序员的意愿进行初始化,这样一个真正可用的对象才算完全产生出来。

四、对象的内存布局

在 Hotspot 虚拟机中,对象在内存中的布局可以分为 3 块区域:对象头实例数据对齐填充

Hotspot 虚拟机的对象头包括两部分信息第一部分用于存储对象自身的运行时数据(哈希码、GC 分代年龄、锁状态标志等等),另一部分是类型指针,即对象指向它的类元数据的指针,虚拟机通过这个指针来确定这个对象是哪个类的实例。

实例数据部分是对象真正存储的有效信息,也是在程序中所定义的各种类型的字段内容。

对齐填充部分不是必然存在的,也没有什么特别的含义,仅仅起占位作用。 因为 Hotspot 虚拟机的自动内存管理系统要求对象起始地址必须是 8 字节的整数倍,换句话说就是对象的大小必须是 8 字节的整数倍。而对象头部分正好是 8 字节的倍数(1 倍或 2 倍),因此,当对象实例数据部分没有对齐时,就需要通过对齐填充来补全。

五、对象的访问定位

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值