专栏首页IT技术小咖从 Java 代码如何运行聊到 JVM 和对象的创建-分配-定位-布局-垃圾回收

从 Java 代码如何运行聊到 JVM 和对象的创建-分配-定位-布局-垃圾回收

Java 代码到底是如何运行的呢?

看下图理解 Java 代码如何运行:

概括一下:程序员小张编写好的 Java 源代码文件经过 Java 编译器编译成字节码文件后,通过类加载器加载到内存中,才能被实例化,然后到 Java 虚拟机中解释执行,最后通过操作系统操作 CPU 执行获取结果。

具体的 javac 编译和类加载器过程请见下图:

本文主要介绍 JVM 内存模型参数设置说明对象创建过程解析、初始 GC。下面请大家进入正题吧

JVM 内存布局是什么样的呢?

简单的说,共有 5 大块,它们分别是堆区(Java Heap)、虚拟机栈(Virtual Machine Stacks)、本地方法栈(Native Method Stacks)、元空间(Meta Spaces)、程序计数器(Program Counter Register)。

如下图所示(先大概了解一下各自区域都存了啥,后面会一一图文解读):

按线程的共享与私有(线程安全)分类:

共享区域:

  • 堆区
  • 元空间

私有区域:

  • 虚拟机栈
  • 本地方法栈
  • 程序计数器

下面从简单的 JVM 划分区域开始说起:

程序计数器

  • 占用的 JVM 内存空间较小
  • 每个线程生命周期内独享自己的程序计数器(内部存放的是字节码指令的地址引用)
  • 不会发生 OOM

虚拟机栈

  • 内部结构是栈帧,每个方法在执行的时候都会创建一个栈帧,用于存储局部变量表,操作数栈,动态链接,方法返回地址等信息
  • 某方法在调用另一个方法是通过动态链接在常量池中查询方法的引用,进而完成方法调用
  • 某方法在调用另一个方法的过程,即是一个栈帧在虚拟机中的入栈到出栈的过程
  • 虚拟机中的方法入栈的顺序和方法的调用顺序是一致的

详细情况请查看下图,一目了然:

对于 JVM 中虚拟机栈参数的设置

-Xss :用于设置栈的大小,栈的大小决定了方法调用的深度。

# 设置线程栈大小为 512k(以字节为单位)
-Xss512k

该区域可能出现 StackOverflowException 栈溢出异常。

本地方法栈

  • 和虚拟机栈类似,内部结构是栈帧,每个 Native 方法执行时创建一个栈帧
  • 该部分没有规定内存大小

堆区

  • 存放 Java 对象和数组
  • 虚拟机中存储空间比较大的区域
  • 可能出现 OOM 异常区域
  • 该区域是 GC 的主要区域,堆区由年轻代和老年代组成,年轻代又分为 Eden 区、S0区(from survivor)、S1 区(to survivor);新生代对应 Minor GC(Young GC),老年代对应 Full GC(Old GC)。

对于 JVM 中堆区参数的设置

# 设置堆区的初始大小
-Xms1024m
# 设置堆区的存储空间最大值,一般与堆区的初始大小相等
-Xmx1024m
# 设置年轻代堆的大小
-Xmn512m
# 设置如下参数,在出现OOM时进行堆转储
-XX:+HeapDumpOnOutOfMemoryError
# 设置以上设置时,需配置以下参数,堆转储文件输出的位置
-XX:HeapDumpPath=/usr/log/java_dump.hprof

方法区与永久代

  • 方法区被所有线程共享。采用永久代的方式实现了方法区。
  • jdk 8 以前(不包括 jdk8)存在永久代(Perm区),jdk 8 以后(包括 jdk 8)移除了永久代。如下图所示。

方法区在不同 JDK 版本的变化

请见下图:

方法区和元空间的区别

请见下图:

对于 JVM 中永久代或元空间参数的设置

# jdk1.7 设置永久代内存初始大小
-XX:PermSize=512m
# jdk1.7 设置永久代内存最大值
-XX:MaxPermSize=512m
# jdk1.8 设置元空间内存初始大小
-XX:MetaspaceSize=1024m
# jdk1.8 设置元空间内存最大值
-XX:MaxMetaspaceSize=1024m

以 ObjectA a = new ObjectA(); 为例

聊一聊,对象在 JVM 虚拟机中是如何创建的,在什么地方分配内存,又是如何分配的,对象是如何定位的,以及对象的内存布局,最后又是如何回收的。

1)对象的创建

先在虚拟机栈创建栈帧,栈帧内创建对象的引用,在方法区进行类的加载,然后去 Java 堆区进行分配内存并内存初始化,再回到栈帧中初始化对象的数据,完成对象的创建。见下图:

2)Java 堆内存分配过程

想要更好的理解 Java 堆区内存分配过程,得先了解内存分配方法有哪些,内存分配方法分为指针碰撞法空闲列表法

  • 指针碰撞法 支持压缩整理功能的垃圾回收器 Serial、ParNew 等(Compact 过程),使得已使用的内存和未使用的内存分开,两者之间存在一个指针作为分界点指示器。 分配内存只需移动指针,分界点指示器向未使用的内存一侧移动一段与对象大小相等的空间,这种分配内存的方法叫做指针碰撞法。如下图所示:
  • 空闲列表法 基于标记清除(Mark-Sweep)算法的 CMS 垃圾回收器,其内存划分成网格区(Region),内存分配不规整,即已使用的和未使用的内存随机分布,JVM 会维护一个记录表,用于记录那些内存可用于分配,当需要给对象分配内存区域时,寻找一块足够大的内存空间分配给对象,并更新记录表,这种分配内存的方法叫做空闲列表法。如下图所示:

Java 堆区对象内存分配

JVM 中内存分配纷繁复杂,为了防止内存分配混乱,需要解决并发问题,解决并发问题有两种方式:同步处理方式TLAB 方式

  • 同步处理:内存分配的动作采用同步机制,JVM 为了增加效率采用了 CAS 方式。

在计算机科学中,比较和交换(Conmpare And Swap)是用于实现多线程同步的原子指令。它将内存位置的内容与给定值进行比较,只有在相同的情况下,将该内存位置的内容修改为新的给定值。这是作为单个原子操作完成的。

  • TLAB 方式:每个线程在 Java 堆中预先分配一小块内存,叫做本地线程分配缓冲区。TLAB 的全称是 Thread Local Allocation Buffer。这个 TLAB 和 Java 中的 ThreadLocal 类有点像,每个线程独享线程本地变量。 哪个线程需要分配内存先去各自的 TLAB 中分配,但是这个缓冲区比较小,是为了加速对象的分配。只有在线程的 TLAB 用完才会去堆中进行内存分配,此时才需要同步机制。如下图所示:

3)对象的访问定位

  • 句柄访问,见下图所示:

注:句柄池是 Java 堆分配用于存放对象指针的内存空间

优点:在垃圾回收的时候对象要经常转移,这时候只需改变句柄中指向对象实例数据的指针即可(不用修改 reference)。

  • 直接访问,见下图所示:

优点:相对于句柄访问定位的方式,减少了一次指针定位的开销(也减少了句柄池的存储空间),HotSpot JVM 实现采用的是直接访问的方式进行对象访问定位。

4)对象的内存布局

对象的组成:对象头(对象自身运行时数据和类型指针)、实例数据和对齐填充。可参考这篇文章(记一次生产频繁出现 Full GC 的 GC日志图文详解)中的第 3 部分关于线上系统 JVM 内存估算方法。如下图所示:

初始 Java GC

这里只做简单了解,如果后面有时间会对 JVM 垃圾回收深入分析。

  • 针对上面 Java 创建对象过程的例子。 ObjectA a = new ObjectA();类似这样创建对象的即是强引用,如果该引用存在,则垃圾回收器就不会回收它。 注:对象引用类型(由强到弱)分为强引用、软引用、弱引用、虚引用。
  • GC 针对的 JVM 区域 从上面对 JVM 内存布局的介绍,发生 GC 主要是针对 Java Heap 区 和 元空间(或方法区)。其他区域都是线程私有的,即随着线程的创建而创建,随着线程的销毁而销毁。
  • 对于 JVM 中 GC 参数的设置
# 在控制台输出GC情况
-verbose:gc 
# GC日志输出
-XX:+PrintGC
# GC日志详细输出
-XX:+PrintGCDetails
# GC输出时间戳
-XX:+PrintGCDateStamps
# GC日志输出指定文件中
-Xloggc:/log/gc.log

小结

从 Java 代码如何运行的,聊到 JVM 内存布局,虚拟机参数的配置说明,Java 对象的创建(new)过程,包括对象内存的堆分配、对象的定位、对象内存布局等,以及最后简单介绍了垃圾回收相关内容。本文以图文并茂的方式分享,希望加速大家的理解和阅读体验,也希望本文能给大家带来一些小小的收获。

本文分享自微信公众号 - IT技术小咖(IT-arch),作者:giserway

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2020-02-21

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • Java 8、9、10以及11的变化

    自1996年JDK 1.0(Java 1.0)发布以来,Java已经受到了学生、项目经理和程序员等一大批活跃用户的欢迎。这一语言极具活力,不断被用在大大小小的项...

    IT技术小咖
  • 分布式系统架构你必会的 Zookeeper 之基础模块-常用命令-核心原理-集群搭建-实战演练(上)

    1)Zookeeper (文中后续简称 ZK)是一个开源的分布式服务协调系统,最初由雅虎公司开发,后成为 Apache 基金会顶级开源项目。

    IT技术小咖
  • redis 集群模式的工作原理能说一下么?在集群模式下,redis 的 key 是如何寻址的?

    在 redis cluster 架构下,每个 redis 要放开两个端口号,比如一个是 6379,另外一个就是 加1w 的端口号,比如 16379。

    IT技术小咖
  • Java面试知识点解析——JVM篇

    Spark学习技巧
  • Java面试知识点解析——JVM篇

    注意:跨平台的是 Java 程序,而不是 JVM。JVM 是用 C/C++ 开发的,是编译后的机器码,不能跨平台,不同平台下需要安装不同版本的 JVM

    Java团长
  • 8张图理解Java

    一图胜千言,下面图解均来自Program Creek 网站的Java教程,目前它们拥有最多的票选。

    Java后端工程师
  • 借助JVM生日的时机,说说关于JVM你所不知道的那些事

    1991年4月,由James Gosling主导的团队创造了Oak语言,java的前身,1995年5月23号,Oak语言更名Java,并且提出那句注明的:”wr...

    技术zhai
  • 3分钟速读原著《深入理解Java虚拟机》(二)

    对于C/C++来说,每个堆内存的new都需要delete/free操作,对于Java来说内存管理已经交给JVM,好处是不用处理内存,坏处是容易出现OOM问题

    cwl_java
  • 快看,VUE对你的页面做了什么

    Virtual DOM中负责将新旧DOM树中的节点进行对比并找出发生变更的节点这一工作是由diff来进行的,diff是Virtual DOM较为核心的部分,要对...

    yuanyi928
  • Java官网曝本地文件包含(LFI)漏洞,可读取超过460位Oracle公司员工邮箱

    微信号:freebuf 意大利安全研究人员Christian Galeone最近发现一枚Java官网存在的重大安全漏洞,该漏洞可读取网站敏感数据,包括超过460...

    FB客服

扫码关注云+社区

领取腾讯云代金券