JVM内存布局和运行原理

在这里插入图片描述

堆:

《Java虚拟机规范》对Java堆的描述是:“所有的对象 实例以及数组都应当在堆上分配”
比如JIT (Just In Time Compilation,即时编译)优化中的逃逸分析
使得变量可以直接在栈上被分配
当对象或者是变量在方法中被创建之后,其指针可能被线程所引用
而这个对象就被称作指针逃逸或者是引用逃逸.
比如以下代码就是指针逃逸, 他在方法中创建了变量 ,值却被其他值引用.

public static  StringBuffer result(){
        StringBuffer buffer = new StringBuffer();
        buffer.append("10");
        return buffer;
    }

所以上面的代码应该改为

public static  String result(){
        StringBuffer buffer = new StringBuffer();
        buffer.append("10");
        return buffer.toString();
    }

通过逃逸分析可以让变量或者是对象直接在栈.上分配从而极大地降低了垃圾回收的次数,以及堆分配对象的压力,进而提高了程序的整体运行效率.

堆大小的值可通过-Xms和-Xmx来设置(设置最小值和最大值)
当堆超过最大值时就会抛出00M (OutOfMemoryError) 异常.

方法区:

方法区(Method Area)也被称为非堆区
用于和“Java 堆”的概念进行区分,它也是线程共享的内存区域
用于存储已经被JVM加载的类型信息、常量、静态变量、代码缓存等数据

“永久代”是 HotSpot中特有的一个概念,HotSpot 技术团队只是用永久代来实现方法区但这会导致一个致命的问题,这样设计更容易造成内存溢出因为永久代有-XX: MaxPermSize (方法区分配的最大内存)的. 上限,即使不设置也会有默认的大小例如,32位操作系统中的4GB内存限制等,并且这样设计导致了部分的方法在不同类型的Java虚拟机下的表现也不同,比如String::intern()方法.

程序计数器(Program Counter Register)

线程独有一块很小的内存区域
保存当前线程所执行字节码的位置
包括正在执行的指令、跳转、分支、循环、异常处理等

虚拟机栈

虚拟机栈也叫Java虚拟机栈(Java Virtual Machine Stack)和程序计数器相同它也是线程独享的
用来描述Java方法的执行,在每个方法被执行时就会同步创建一个栈帧用来存储局部变量表、操作栈、动态链接、方法出口等信息当调用方法时执行入栈,而方法返回时执行出栈.

本地方法栈(Native Method Stacks)

与虚拟机栈类似
它是线程独享的,并且作用也和虚拟机栈类似
只不过虚拟机栈是为虚拟机中执行的Java方法服务的
本地方法栈则是为虚拟机使用到的本地(Native) 方法服务
tip:
需要注意的是《Java虚拟机规范》只规定了有这么几个区域
但没有规定JVM的具体实现细节,因此对于不同的JVM来说,实现也是不同的
例如,
“永久代”是HotSpot中的-一个概念,而对于JRockit来说就没有这个概念
所以很多人说的JDK 1.8把永久代转移到了元空间,这其实只是HotSpot的实现
而非《Java虚拟机规范》的规定
在这里插入图片描述

类加载顺序
在这里插入图片描述
验证、准备、解析这三个阶段统称为连接阶段.
在这里插入图片描述

  • 加载阶段 此阶段用于查到相应的类(通过类名进行查找)并将此类的字节流转换为方法区运行时的数据结构 ,然后再在内存中生成一个能代表此类的java.lang.Class对象,作为其他数据访问的入口.需要注意的是加载阶段和连接阶段的部分动作有可能是交叉执行的比如一部分字节码文件格式的验证,在加载阶段还未完成时就已经开始验证了

  • 验证阶段 此步骤主要是为了验证字节码的安全性
    如果不做安全校验的话,可能会载入非安全或有错误的字节码
    从而导致系统崩溃,它是JVM自我保护的一项重要举措 .主要包括以下几种验证方式.文件格式验证、元数据验证、字节码验证、符号引用验证

  • 文件格式验证 验证点:
    (1)是否以魔数0xCAFEBABE开头。
    (2)主、次版本号是否在当前虚拟机处理范围之内。
    (3)常量池的常量中是否有不被支持的常量类型(检查常量tag标志)。
    (4)指向常量的各种索引值中是否有指向不存在的常量或不符合类型的常量。
    (5)CONSTANT_Utf8_info型的常量中是否有不符合UTF8编码的数据。
    (6)Class文件中各个部分及文件本身是否有被删除的或附加的其他信息(文件是否完整.)

  • 元数据验证 验证点:
    (1)这个类是否有父类(除了java.lang.Object之外,所有类都应当有父类)。
    (2)这个类是否继承了不允许被继承的类(被final修饰的类)。
    (3)如果这个类不是抽象类,是否实现了其父类或接口之中所要求实现的所有方法。
    (4)类中的字段、方法是否与父类产生矛盾(例如覆盖了父类的final字段,或者出现不符合规则的方法重载,例如方法参数都一致,但返回值类型却不同等等)。

  • 字节码验证 验证点:
    (1)保证任意时刻操作数栈的数据类型与指令代码序列都能配合工作,例如不会出现类似这样的情况:在操作数栈放置了一个int类型的数据,使用时却按long类型来加载入本地变量表中。
    (2)保证跳转指令不会跳转到方法体以外的字节码指令上。
    (3)保证方法体中的类型转换是有效的,例如可以把一个子类对象赋值给父类数据类型,但是把父类对象赋值给子类数据类型,甚至把对象赋值给与它毫无继承关系、完全不相干的一个数据类型,则是危险不合法的。

  • 符号引用验证 验证点:
    (1)符号引用中通过字符串描述的全限定名是否能够找到对应的类。
    (2)在指定类中是否存在符合方法的字段描述符以及简单名称所描述的方法和字段。
    (3)符号引用中的类、字段、方法的访问性(private、protected、public、default)是否可被当前类访问

在这里插入图片描述

准备阶段

此阶段是用来初始化并为类中定义的静态变量分配内存的,这些静态变量会被分配到方法区上HotSpot虚拟机在JDK 1.7之前都在方法区
而JDK1.8之后此变量会随着类对象一起存放到Java堆中.

解析阶段

此阶段主要是用来解析类、接口、字段及方法的,解析时会把符号引用替换成直接引用符号引用是指以一组符号来描述所引用的目标,符号可以是任何形式的字面量.只要使用时能无歧义地定位到目标即可
直接引用是可以直接指向目标的指针、相对偏移量或者是一一个能间接定位到目标的句柄.

符号引用和直接引用区别:使用符号引用时,被引用的目标不一定已经加载到内存中. 使用直接引用时,引用的目标必定已经存在虚拟机的内存中了.

初始化阶段

初始化阶段JVM就正式开始执行类中编写的Java业务代码了
这一步骤之后,类的加载过程就算正式完成了

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值