《码出高效-Java开发手册》走进JVM有感

走进JVM

字节码

Java 所有的指令有200个左右,一个字节可以存储256种不同的指令信息,一个这样的字节称为字节码(中间码)。在代码的执行过程中,JVM将字节码解释执行,屏蔽对底层操作系统的依赖,JVM 也可以将字节码编译执行,如果是热点代码,会通过JIT 动态地编译为机器码,提高执行效率

字节码主要指令如下:

加载或存储指令

在某个栈帧中,通过指令操作数据在虚拟机栈的局部变量表与操作栈之间来回传输,常用指令如下:

将局部变量加载到操作栈中。

从操作栈顶存储到局部变量表

将常量加载到操作栈顶,这是极为高频使用的指令

运算指令: 对两个操作栈帧上的值进行运算,并把结果写入操作栈顶IADD、IMUL

类型转换指令 显示转换两种不同的数值类型

对象创建于访问指令

创建对象指令 NEW NEWARRAY

访问属性指令 GETFIELD、PUTFIELD 等

检查实例类型指令

操作栈管理指令

出栈指令

赋值栈顶元素并压入栈

方法调用与返回指令

详细见 P125《码出高效: JAVA开发手册》

Java源文件 ----> 词法解析-- token流 -> 语法解析 -----> 语义分析 -----> 生成字节码 ----> 字节码

详细见P126

类加载过程

任何程序都需要加载到内存中才能与CPU进行交流。字节码 .class 文件同样需要家长到内存中,才可以实例化类。ClassLoader 就是提前加载 .class 文件到内存中

过程: 加载、链接、初始化

加载: 读取类文件产生的二进制流,并转为特定的数据结构,初步校验cafe babe 魔法值,常量池、文件长度、是否有父类等,然后创建对应类的实例

链接包括验证、准备、解析三个过程。验证是更详细的校验,比如final 是否合规,类型是否正确、静态变量是否合理等;准备结果是为静态变量分配内存,并设定默认值,解析类和方法确保类与类之间的相互引用正确性,完成内存结构布局

初始化结果,执行类构造器方法,如果赋值运算是通过其他类的静态方法来完成的,那么会马上解析另一个类,在虚拟机栈中执行完毕后通过返回值进行赋值

内存布局

Heap (堆区)

是OOM 故障的主要发源地,它存储着几乎所有的实例对象,堆由垃圾收集器自动回收,堆区各子线程共享使用。通常它的占用的空间是所有内存区域最大的,但是如果无节制的创建实例那么也将会消耗完内存导致OOM。可以在运行时动态的设置它的大小,-Xms256M - Xmx1024M 表示设定初始值和最大值。服务器在运行过程中,退空间不断地扩容和回缩,势必形成不必要的系统压力,所以在线上生产环境中,JVM 的Xms和Xmx 设置为一样大小,避免在GC 后调整堆大小时带来的额外压力。

(下图:这里的放得下指的是当创建一个大对象时候,内存区域是否能够容纳得下)

Metaspace(元空间)

元空间的前身是Perm区(被称为永久代),在JDK7 及之前的版本中才有Perm,现在的版本使用了Metaspace。因为Perm 在某些场景下,如果动态加载类过多,容易产生Perm 区的OOM(为了解决需要设定参数 -XX:MaxPermSize = 1280m),如果部署到新机器上,往往会因为JVM 参数没有修改导致故障再现,不熟悉此应用的人很难排查。所以,元空间就诞生了。元空间在本地内存中分配。

JVM Stack (虚拟机栈)

栈的特性是先进后出的数据结构,JVM 是基于栈结构的院系环境。JVM 中的虚拟机栈是描述Java方法执行的内存区域,它是线程私有的。栈中的元素用于支持虚拟机进行方法调用,每个方法从开始调用到执行完成的过程。就是栈帧从入栈到出栈的过程。活动线程中只有位于栈顶的帧才是有效的,称为当前栈帧。正在执行的方法称为当前方法。

虚拟机栈通过压栈和出栈的方式,对每个方法对应的活动栈帧进行运算处理,方法正常执行结束,肯定会跳转到另一个栈帧上。在执行过程中,如果出现异常,会进行异常回溯,返回地址通过异常处理表确定。

局部变量表: 存放方法参数和局部变量的区域

操作栈 : 一个初始状态为空的桶式结构栈。在方法执行过程中,会有各种指令往栈中写入和提取信息。

动态连接:每一个栈帧包含一个常量池中对当前方法的引用,目的是支持方法调用过程的动态连接

方法返回地址: 方法执行有两种退出情况: 1. 正常退出。2.异常退出。 无论何种退出情况都将返回到方法当前被调用的位置。方法退出的过程相当于弹出当前栈帧。

本地方法栈

Native Method Stack 在JVM内存布局中,也是线程对象私有的。被称为Native 方法服务,线程开始调用本地方法时,会进入一个不再受JVM约束的世界。本地方法可以通过JNI来访问虚拟机运行时的数据区,甚至可以调用寄存器,具有和JVM相同的能力和权限。

程序计数寄存器

每一个线程在创建后,都会产生自己的程序计数器和栈帧,程序计数器用来存放执行指令的偏移量和行号指示器,程序的执行或者恢复都要依赖程序计数器。程序计数器在哥线程之间互不影响,此区域不会发生内存溢出异常。

从线程共享的角度来看,堆空间和元空间都所有线程共享的,而虚拟机栈和本地方法栈,程序计数器是线程内部私有的。

对象实例化

实例化对象过程

确认类元信息是否存在。当JVM接受到new指令时,首先在metaspace 内检查需要创建的类元信息是否存在。若不存在,那么在双亲委派模式下,使用当前类加载器以ClassLoader +包名+类名为key 进行查找对应的.class 文件,如果没有找到文件,则抛出ClassNotFoundException 异常,如果找到,则进行类加载并生成对应的Class 类对象。

分配对象内存。首先计算对象占用空间大小,如果实例成员变量是引用变量,仅分配引用变量空间即可,即4个字节大小,接着在堆中划分一块内存给新对象。在分配内存空间时,需要进行同步操作,比如采用CAS失败重试,区域加锁等方式保证分配操作的原子性。

设定默认值。成员变量值都需要设定默认值,即各种不同形式的零值。

设置对象头。设置新对象的哈希码,GC信息,锁信息,对象所属的类元信息等。这个过程的具体设置方式取决JVM实现。

执行init 方法。初始化成员变量,执行实例化代码块,调用类的构造方法,并把堆内对象的首地址赋值给引用变量。

垃圾回收

Java 会对内存进行自动分配与回收管理,使上层业务更加安全,方便地使用内存实现程序逻辑。GC 主要目的是清除不再使用的对象,自动释放内存。

标记-清除

复制

标记-整理

区分新老年代(分代收集)

但最重要的是不知道哪些技术需要重点掌握,学习时频繁踩坑,最终浪费大量时间,所以有一套实用的视频课程用来跟着学习是非常有必要的。

为了让学习变得轻松、高效,今天给大家免费分享一套阿里架构师传授的一套教学资源。帮助大家在成为架构师的道路上披荆斩棘。

而且还把框架需要用到的各种程序进行了打包,根据基础视频可以让你轻松搭建分布式框架环境,像在企业生产环境一样进行学习和实践。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值