看下图理解 Java 代码如何运行:
概括一下:程序员小张编写好的 Java 源代码文件经过 Java 编译器编译成字节码文件后,通过类加载器加载到内存中,才能被实例化,然后到 Java 虚拟机中解释执行,最后通过操作系统操作 CPU 执行获取结果。
具体的 javac 编译和类加载器过程请见下图:
本文主要介绍 JVM 内存模型、参数设置说明、对象创建过程解析、初始 GC。下面请大家进入正题吧
简单的说,共有 5 大块,它们分别是堆区(Java Heap)、虚拟机栈(Virtual Machine Stacks)、本地方法栈(Native Method Stacks)、元空间(Meta Spaces)、程序计数器(Program Counter Register)。
如下图所示(先大概了解一下各自区域都存了啥,后面会一一图文解读):
按线程的共享与私有(线程安全)分类:
共享区域:
私有区域:
下面从简单的 JVM 划分区域开始说起:
详细情况请查看下图,一目了然:
-Xss :用于设置栈的大小,栈的大小决定了方法调用的深度。
# 设置线程栈大小为 512k(以字节为单位)
-Xss512k
该区域可能出现 StackOverflowException 栈溢出异常。
# 设置堆区的初始大小
-Xms1024m
# 设置堆区的存储空间最大值,一般与堆区的初始大小相等
-Xmx1024m
# 设置年轻代堆的大小
-Xmn512m
# 设置如下参数,在出现OOM时进行堆转储
-XX:+HeapDumpOnOutOfMemoryError
# 设置以上设置时,需配置以下参数,堆转储文件输出的位置
-XX:HeapDumpPath=/usr/log/java_dump.hprof
请见下图:
请见下图:
# jdk1.7 设置永久代内存初始大小
-XX:PermSize=512m
# jdk1.7 设置永久代内存最大值
-XX:MaxPermSize=512m
# jdk1.8 设置元空间内存初始大小
-XX:MetaspaceSize=1024m
# jdk1.8 设置元空间内存最大值
-XX:MaxMetaspaceSize=1024m
聊一聊,对象在 JVM 虚拟机中是如何创建
的,在什么地方分配内存
,又是如何分配
的,对象是如何定位
的,以及对象的内存布局
,最后又是如何回收
的。
先在虚拟机栈创建栈帧,栈帧内创建对象的引用,在方法区进行类的加载,然后去 Java 堆区进行分配内存并内存初始化,再回到栈帧中初始化对象的数据,完成对象的创建。见下图:
想要更好的理解 Java 堆区内存分配过程,得先了解内存分配方法有哪些,内存分配方法分为指针碰撞法和空闲列表法。
JVM 中内存分配纷繁复杂,为了防止内存分配混乱,需要解决并发问题,解决并发问题有两种方式:同步处理方式
和 TLAB 方式
在计算机科学中,比较和交换(Conmpare And Swap)是用于实现多线程同步的原子指令。它将内存位置的内容与给定值进行比较,只有在相同的情况下,将该内存位置的内容修改为新的给定值。这是作为单个原子操作完成的。
注:句柄池是 Java 堆分配用于存放对象指针的内存空间。
优点:在垃圾回收的时候对象要经常转移,这时候只需改变句柄中指向对象实例数据的指针即可(不用修改 reference)。
优点:相对于句柄访问定位的方式,减少了一次指针定位的开销(也减少了句柄池的存储空间),HotSpot JVM 实现采用的是直接访问的方式进行对象访问定位。
对象的组成:对象头(对象自身运行时数据和类型指针)、实例数据和对齐填充。可参考这篇文章(记一次生产频繁出现 Full GC 的 GC日志图文详解)中的第 3 部分关于线上系统 JVM 内存估算方法。如下图所示:
这里只做简单了解,如果后面有时间会对 JVM 垃圾回收深入分析。
ObjectA a = new ObjectA();
类似这样创建对象的即是强引用
,如果该引用存在,则垃圾回收器就不会回收它。
注:对象引用类型(由强到弱)分为强引用、软引用、弱引用、虚引用。# 在控制台输出GC情况
-verbose:gc
# GC日志输出
-XX:+PrintGC
# GC日志详细输出
-XX:+PrintGCDetails
# GC输出时间戳
-XX:+PrintGCDateStamps
# GC日志输出指定文件中
-Xloggc:/log/gc.log
从 Java 代码如何运行的,聊到 JVM 内存布局,虚拟机参数的配置说明,Java 对象的创建(new)过程,包括对象内存的堆分配、对象的定位、对象内存布局等,以及最后简单介绍了垃圾回收相关内容。本文以图文并茂的方式分享,希望加速大家的理解和阅读体验,也希望本文能给大家带来一些小小的收获。