JVM整理(二)

JVM介绍

运行时数据区

运行时数据区可以分为:方法区、堆、虚拟机栈、本地方法栈、程序计数器.其中方法区和堆为线程共享区,虚拟机栈、本地方法栈和程序计数器为线程独享区.线程独享区的内存空间随线程的创建被开辟,随线程的销毁被回收,所以垃圾回收器主要是作用在方法区和堆区.
JVM整理(二)_第1张图片

堆可以被分为新生代和老年代,其中新生代由可以分为eden区和surrivor1、surrrivor2区.老年代和新生代默认占比为2:1,可以通过-XX:NewRatio=2配置重新设置占比.Eden和S1、S2的占比为8:1:1,可以通过-XX:NewSurrivor=4配置重新设置占比.
JVM整理(二)_第2张图片

堆大小设置

-Xms:256M 设置JVM的初始内存大小,默认是为系统内存的1/64之一;
-Xmx:256M 设置JVM的最大内存大小,默认是为系统内存的1/4之一;
通常我们优化是将初始化内存和最大内存设置为一样的值,这样可以避免不必要的major gc或full gc,造成STW,引起程序应用不必要的卡顿.

回收算法

新生代一般都会采用复制算法,该算法的效率较高且满足新生代的对象特性.当新生代数据区满时,一般情况下新生代百分之八十左右的数据都可能被回收掉,留下的数据和S1区域会被回收的数据一起复制到S2区.第二次放生minor gc时,eden和S2存活的对象会被复制到S1区,默认情况下,存活的对象默认情况下达到15时,下次发生minor gc时,该存活的对象会放到老年代中.可以通过-XX:MaxTenuringThreshold=15配置重新设置新生代对象的最大年龄.
老年代会采用标记清除或标记整理算法.标记清除会产生内存空间碎片,需要额外维护一个空闲列表,但相较于标记整理算法会快一些.标记整理是先将标记存活的对象都压缩到内存的一端,最后在清理掉边界之外的所有空间,所以不会产生内存碎片.

方法区

方法区是JVM规范,不同jdk版本之间的实现各有不同.jdk1.7及以前,方法区的实现是永久代(Permanent Generation),可以通过-XX:Permsize和-XX:MaxPermsize来分配永久代的初始空间和最大可分配空间.JDK8采用元空间来替换永久代,最大的区别是元空间不在JVM设置的内存中,而是使用本地内存,可以通过-XX:MetaspaceSize和-XX:MaxMetaspaceSize指定元空间的初始和最大空间.
方法区内存放的结构为类信息和运行时常量池,字符串常量池、静态变量在1.7及以后被划分到了堆中.该区也会发生垃圾回收处理.

虚拟机栈默认大小为1M,当遇到递归次数较多的方法,可能会发生StackOverFlowError异常,可以通过-Xss100m指令配置调整栈内存大小.
每个线程都会对应一个虚拟机栈,多个线程就会对应多个虚拟机栈.一个虚拟机栈里面会包含多个栈帧,每一个栈帧是为方法执行而创建的,栈帧中描述的是java方法执行的内存模型.
栈帧中包含局部变量表、操作数栈、动态链接、返回地址.
JVM整理(二)_第3张图片

局部变量表

它定义为数字数组,主要用于存储方法参数和定义在方法内的局部变量.局部变量表所需的容量在编译期间确定,在运行期间是不改变其容量.

操作数栈

它是一个后进先出的栈,根据字节码指令,往栈中写入或取出数据.操作包括:复制、交换、求和等.

动态链接

被调用的方法在编译期间无法被确定下来,只能在程序运行时将调用方法的符号引用转换为直接引用,由于这种引用转换的过程具备动态性,被称为动态链接.

方法返回地址

方法在返回的时候需要在栈帧中保存一些信息,用来恢复调用该方法的上层方法的执行状态.

程序计数器

程序计数器就是一块较小的内存空间,它是当前线程执行字节码的行号指示器.每个栈帧都会维护一个属于自己的程序计数器,这个计数器就是用来记录执行的地址的.

本地方法栈

本地方法栈和虚拟机栈类所发挥的作用非常类似.它们之间的区别就是虚拟机栈为虚拟机执行Java方法服务,而本地方法栈为虚拟机所使用到的native方法服务.本地方法只有被调用之前,DLL才会被加载,即通过调用java.system.loadLibrary()实现的.

[下一章节 介绍执行引擎]

你可能感兴趣的:(java)