月初的时候个人网站到期了,不想再折腾重新建站了,以后还是来第三方博客写文章吧,可以省去很多问题。之前写的文章也不是很多,备份懒得做了,从头开始吧。博文仅仅是用来记录和学习总结,如有错误之处请帮忙指正!
今天想说说JVM内存结构的问题,说到JVM大家肯定首先想到的是栈和堆。的确,这两块说是JVM内存结构最重要的部分也不为过。先来简单介绍一下吧,
内存结构按照私有和共享划分方式如下:
线程私有:栈区、本地方法栈、程序计数器
线程共享:堆区、方法区
其他不说了,重点说说栈和堆
栈:
栈的特点就是快。每个线程对应一个栈,每个栈含有1个或多个栈帧,栈帧用来存放方法的局部变量表、操作数栈、动态链接、returnAddress等信息,每运行一个方法就会创建一个栈帧,从方法运行到结束对应着栈帧在栈区里面入栈和出栈的过程。
局部变量表包含两类数据结构,一是八大基本类型,byte short int long float double char boolean; 第二类是reference类型,占用空间是4byte,不管是对象是大是小,操控它仅需要用一个4byte的变量即可,而且可以按需销毁,这足以体现栈堆分离的好处。
栈空间是可以重复利用的,遇到左括号入栈,遇到右括号出栈,我们当系统进行递归调用时,系统会连续多次执行入栈操作,直到最深处才执行出栈操作,这就可能导致栈空间不足,StackOverFlowError异常,因此遇到该异常一般不是对象太大导致的,多是因为不正确递归或栈空间不足以容纳导致。在栈区还可能会遇到OOM异常,当线程过多时或分配空间过小时,如果无法申请到足够内存时,便会报OOM异常(栈区OOM异常的原因暂不确定也为遇见过,只是周志明JVM书中提到栈区OOM异常,上网也未查到)。JVM调优:通过-Xss来控制栈空间大小。
堆:
堆是JVM内存中最大的一块,是用来为对象和数组元素分配空间的地方,而对象的引用变量和数组引用都是在栈中存放的。“几乎所有的对象都在这里创建”,那么什么情况下对象不在堆中创建呢?随着JIT编译器的发展,在编译期间,如果JIT经逃逸分析后发现对象没有逃逸出方法,那么该对象坑会在栈上分配内存而不是堆,但是也不绝对。测试:为JVM分配足够空间,关闭逃逸分析,创建一百万个User对象,jmap命令发现堆中创建了一百万个对象,开启逃逸分析,发现堆中的对象变成了八万个,也就是说JIT编译器确实会将对象分配在栈里,但并不绝对。
使用逃逸分析,编译器可以针对分析结果做以下优化:
1、同步省略(锁消除优化):如果一个对象被发现只能被一个线程访问,那么可以不考虑该对象的同步;
2、栈上分配对象;如果一个对象在子程序中被分配,要是指向该对象的指针永远不会逃逸,对象可能会在栈中被分配,而不是在堆中;
3、分离对象或标量替换:有的对象可能不需要连续的内存结构,可以将该对象的部分或全部存储到CPU的寄存器中。
逃逸分析:-XX:+/-DoEscapeAnalysis
关于堆的分代和垃圾收集下次再讲。
顺带讲一下,参数传递的时候,Java是传值还是传引用?这个问题相信每个程序员都会思考过,也肯定遇到过由此引发的问题。
要说明这个问题,首先要说明两点:一是不要和C语言类比,Java没有指针的概念;二是程序运行永远都是在栈中进行,因而参数传递时涉及到的只会是基本数据类型和引用类型,理论上来说不会涉及到对象本身。而Java中没有指针的概念,因此Java可以说是传值调用,当参数是引用类型时传递的是引用变量的值;但是当进入被调用方法时,被传递的这个引用的值,被程序解释(或者查找)到堆中的对象,这个时候才对应到真正的对象,这个时候如果对其进行修改,修改的并不是引用本身,而是对象的属性,所以对对象的修改会保持。程序参数传递时,被传递的值本身都是不能进行修改的,但是,如果这个值是一个引用,则可以修改这个引用背后的东西。