参考来源:http://www.codeceo.com/article/jvm-memory-stack.html
JAVA能够实现跨平台的根本原因之一就是定义了class文件的格式标准。 任何实现该标准的 JVM 都可以加载和解释类文件。 由此,我们也可以知道为什么Java语言的执行速度比C/C++语言的执行速度慢。
当然,原因肯定不止一个。 比如JVM中没有数据寄存器,指令集使用栈来保存中间数据……等等。虽然Java的贡献者想了各种办法来提高执行速度,比如JIT和动态编译下面是Leetcode中的一个题目用不同语言实现时的执行性能对比图…
下面是JVM的基本架构图。 在这个基本架构图中,栈有两部分,Java线程栈和本地方法栈。 栈的概念与C/C++程序基本相同。 栈帧,一个栈帧代表一次函数调用,里面存放了函数的形参、函数的局部变量、返回地址等。
但是与C/C++的一个重要区别是,在C/C++中按值传递和按地址传递是有区别的。 传递一个对象的时候(结构体也可以看做是一个对象,其实就是一个对象~,但是里面的方法都是public的,不信你可以试试,如果你加一个函数到结构体,编译器不会报错,程序依然会运行~~~),对象会被复制到栈中,而Java中只有基本类型可以传值,其他类型都是传值引用。
什么是参考? 如果你学过C/C++,应该把引用理解为指针~~~ 在这张基础架构图中,可以看到JVM还定义了一个本地方法栈,用于Java调用本地方法。 [这些本地方法是由其他语言编写的]服务。
上图中可以看到JVM有两个栈,但是只有一个堆,每个线程都有自己的线程栈【线程栈的大小可以通过设置JVM的-xss参数来配置,32位系统下,一般默认大小为512K]。
线程栈中的数据是线程私有的,但所有线程共享一个堆空间。 对象数据存储在堆中。 什么是对象数据? 排除法,除了基本类型和引用类型之外的数据都会放在堆空间,下面我们来详细分析一下堆空间……
JVM中的堆空间划分如下图所示
上图描绘了Java程序运行时的堆空间,可以简单描述为以下两项
1、JVM中的堆空间可以分为三大区,新生代,老年代,永久代
2.新生代可分为三个区域,伊甸园区域和两个幸存者区域
JVM运行时,可以通过配置如下参数改变整个JVM堆的配置比例
1.JVM运行时堆的大小
-Xms堆的最小值
-Xmx堆空间的最大值
2.新生代堆空间大小调整
-XX:NewSize新生代的最小值
-XX:MaxNewSize新生代的最大值
-XX:NewRatio设置新生代与老年代在堆空间的大小
-XX:SurvivorRatio新生代中Eden所占区域的大小
3.永久代大小调整
-XX:MaxPermSize
4.其他
-XX:MaxTenuringThreshold,设置将新生代对象转到老年代时需要经过多少次垃圾回收,但是仍然没有被回收
在上面的配置中,老年代占用的空间大小是通过参数-XX:SurvivorRatio配置的。 看完上面的JVM堆空间分配图,大家可能会觉得奇怪。
为什么新生代空间分为三个区,Eden和两个Survivor区? 重点是什么? 为什么要这么划分呢? 要理解这个问题,就得理解JVM的垃圾回收机制(复制算法也叫copy算法)。 步骤如下:
复制算法
将内存平均分为A和B,算法过程:
1.新生的对象被分配到块A中未使用的内存中,当块A的内存用完时,将块A中存活的对象复制到块B中。
2.清理Block A中的所有对象。
3.在分配新对象的B块中未使用的内存中。 当B块的内存用完后,将B块的存活对象复制到A块中。
4. 清理 Block B 中的所有对象。
5.转到1。
优点:简单高效。 缺点:内存成本高,有效内存是占用内存的一半。
图解说明如下:(图中后视图为循环过程)
进一步优化复制算法:使用Eden/S0/S1三个分区
平均分成A/B块太浪费内存了。 使用Eden/S0/S1三个区域比较合理。 空间比例为Eden:S0:S1==8:1:1,有效内存(即新生对象的内存)为总内存的9/10。
算法过程:
1、Eden+S0可以分配新的对象;
2、对Eden+S0进行垃圾回收,将存活的对象复制到S1。 清理 Eden+S0。 新一代 GC 结束。
3、Eden+S1可以分配新生对象;
4、对Eden+S1进行垃圾回收,将存活的对象复制到S0。 清理 Eden+S1。 第二次新生代GC结束。
5.转到1。
默认的Eden:S0:S1=8:1:1java栈和堆分别存放什么,因此新生代中的可用内存空间占新生代的9/10,所以有人会问,为什么不直接分成两个区,一个区占9/10 /10,其他区域占1/10,这大概有以下原因
1、S0和S1的间隔明显变小,有效新生代空间为Eden+S0/S1,所以有效空间大,增加了内存占用
2、有利于对象生成的计算。 当一个对象在S0/S1中达到设定的XX:MaxTenuringThreshold值时,就会被划分到老年代。 想象一下,如果没有S0/S1,会直接分成两个区域。 如何计算对象通过GC多少次还没有被释放。
你可能会说,给对象加一个计数器来记录通过的GC次数,或者有一个映射表来记录对象和GC次数的关系。 是的,这是可能的,但在这种情况下,将扫描整个新生代中的对象。 有了S0/S1java栈和堆分别存放什么,我们只能扫描S0/S1区域~~~
↑↑↑长按图片识别二维码关注↑↑↑