方法区
JVM在执行某个类之前需要先加载这个类(加载,验证,准备,解析,初始化),加载类会将class文件中的类信息(版本,字段,方法,接口,常量池)存入方法区中,文件的常量池用于存放字面变量和符号引用。
- 字面变量包括8大基本数据(byte,short,int,long,float,double,char,boolean)和String
- 符号引用则是类和方法的全限定名,字段及方法的名称和描述符号
- 在类加载完成后文件常量池中的信息将存放到方法区的运行时常量池,并会把符号引用替换为直接引用(内存优化,将相同内容的引用指向同一块内存,比方说相同的方法全限定名)。
- jdk7及以前是以永久代的思想实现方法区,jdk8之后废弃了这个概念,使用元空间来替代,元空间使用的是本地内存,不设置/不限制大小的话只受本机内存的限制。永久代由于设计缺陷限制了默认的内存大小,且其回收的效率很低,导致其容易产生java.lang.OutOfMemoryError: PermGen space。
- 跟堆内存一样这个区域是线程共享的,jvm不愿将其并入堆内存中,从结构上来看,如果合并将会影响堆内存的垃圾回收效率,不管是永久代还是元空间都存在其垃圾回收逻辑,只不过它的效率不高,一般很少关注
堆内存
堆是jvm上最大的内存块,我们创建的基本所有对象都储存在堆中,当然不包括栈内存中的对象,程序启动时就会申请一块堆内存,但不一定所有的内存都会被使用。
- 随着程序的运行,堆内存的使用率增长情况下,为保证程序的内存空间足够使用,jvm会不定期的对不再使用的对象进行回收,这个过程就是GC。
- 运行时一个对象创建时,对于基本数据类型的数据来说,它会直接在栈中分配内存创建,除此之外的大部分对象将在堆中分配内存创建,并在栈中创建一个变量储存该对象的指针。
- 有一种特殊的逃逸对象,对于不会发生线程逃逸的对象,jvm为了效率可以将其直接分配在栈上,该对象生命周期将跟随线程,不跟随堆做垃圾回收,提高性能。
- 堆内存是线程共享的区域
- 可以通过参数配置最小值/最大值
堆大小参数:
-Xms:堆的最小值;
-Xmx:堆的最大值;
-Xmn:新生代的大小;
-XX:NewSize;新生代最小值;
-XX:MaxNewSize:新生代最大值;
例如- Xmx256m
逃逸
- 逃逸分析的原理:分析对象动态作用域,当一个对象在方法中定义后,它可能被外部方法所引用,比如:调用参数传递到其他方法中,这种称之为方法逃逸,甚至还有可能被外部线程访问到,例如:赋值给其他线程中访问的变量,这个称之为线程逃逸。
- 从不逃逸到方法逃逸到线程逃逸,称之为对象由低到高的不同逃逸程度。
- 逃逸分析jvm是默认开启的
栈-虚拟机栈
首先栈是先进后出的结构,虚拟机栈跟线程的生命周期一致,所以他是线程私有的,它储存线程运行时的数据,指令以及返回地址。
- 每个方法在运行时都会产生一个栈帧并入栈,一旦方法执行完成便会出栈,所以当一个栈的所有栈帧都出栈后这个线程就结束了。
- 每个栈帧都包括四个区域,局部变量表,操作数栈,动态链接和返回地址。可用-Xss调整栈帧的大小,默认是1m
- 局部变量表即栈帧储存对象的区域,主要是由8大数据类型及对象引用组成,局部变量是一个32位长度的,如果数据类型是64位则采用高低位占用两个局部变量来存放
- 操作数栈,主要用于保存计算过程中的中间结果,同时作为计算过程中变量临时的存储空间。
- 动态链接时java的语言特性,只有在运行时才能确定具体的方法
- 返回地址,正常返回(调用程序计数器中的地址作为返回)、异常的话(通过异常处理器表<非栈帧中的>来确定)
栈-本地方法栈
- 本地方法栈跟虚拟机栈的方法类似,区别在于本地方法栈管理的是native方法,虚拟机栈管理的是java方法
程序计数器
是一块较小的内存空间,属于线程私有内存,管理当前线程执行字节码的行号指示器。线程运行中的分支、循环、跳转、异常、线程恢复等都依赖于计数器。
常见问题解析
- 什么是堆和栈
功能区别
- 栈通常是以栈帧的形式来存储方法运行过程中创建的8大基本数据类型(byte(8),short(16),int(32),long(64),float(32),double(64),char(16),boolean(1))以及对象的引用