运行时数据区 Runtime Data Area

程序计数器 Program Counter Register

生命周期:与线程相同

占用空间:非常小

多线程:线程私有

存储:当前栈帧的下一步操作指令的地址,及当前线程执行的字节码的行号

GC和Error:没有GC和OOM

Java的多线程是通过轮流切换并分配处理器执行时间的方式来实现,任何一个时刻,一个处理器(多核处理器指一个内核)都只会执行一条线程中的指令。为了线程切换后能恢复到正确的执行位置,每条线程都需要一个独立的程序计数器,各线程计数器互不影响,独立存储。栈帧为java方法,存储字节码指令的地址;执行Native方法为空(Undefined)。

图中的操作指令可由【javap -v class文件名】指令得到

Java虚拟机栈

生命周期:与线程相同

占用空间:可设置(-Xss256k)

多线程:线程私有

存储:栈帧(局部变量表、操作数栈、动态链接、方法返回地址、一些附加信息)

GC和Error:没有GC,StackOverflowError(大小固定),OutOfMemoryError(可扩展)

局部变量表 local variables

数组,存储方法的参数和在方法中定义的局部变量,数据类型包括基本数据类型、对象引用(reference类型,不是对象本身,可能是指向对象起始地址的指针)以及returnAddress类型(指向一条字节码指令的地址)。

其中64位bite的long和double占两个局部变量空间(Slot),其余的数据占用一个。编译期可确定容量大小,且运行期不会。表中的变量只在当前方法调用中有效。方法嵌套调用的次数由栈大小决定。栈越大,方法嵌套嗲用次数越多。非静态方法在局部变量表索引位0的位置默认放this变量。

下图为jclasslib解析的图,以及javap -v LocalVariableTest.class反编译的文件信息

补充:

变量分类:

按照数据类型:1.基本数据类型 2.引用数据类型

按照在类中的声明位置:

1.成员变量:在使用之前都经历过默认初始化赋值

        Static修饰类变量:linking的prepare阶段赋默认值;initial阶段显示赋值

        实例变量:随着对象的创建,在对空间中分配实例变量空间,并默认赋值

2.局部变量:使用前,必须要显示赋值。否则编译不通过

        在栈帧中,局部变量表与性能调优关系最密切。执行时,完成方法传递

        局部变量表中的变量是重要的垃圾回收根节点,只要变量被引用则不会被回收

操作数栈 operand stack

数组实现,但是栈只有后进先出的操作。

存储计算过程中的中间结果,同时作为临时存储空间。

在编译期确定栈深度,存放与code属性中的max_stack值。一旦创建,长度不变。

32bit的类型占用一个栈深度,64bit占用两个栈深度

在方法之心过程中,根据字节码指令,往栈中写入或者提取数据,及入栈push/出栈pop。

若方法有返回值,其返回值将会被压入当前栈帧的操作数栈中。

动态链接 dynamic linking

每个栈帧都包含一个执行运行常量池中栈帧所属方法的引用。在字节码文件中,所有的变量和方法的引用都作为符号引用保存在class文件的常量池中。动态链接就是将这些符号引用转换为调用方法的直接引用

静态链接:被调用目标方法在编译期可知,运行期保持不变。

动态链接:被调用目标方法在编译期不可知,运行期将符号引用转为直接引用。

对应的方法绑定机制:

早期绑定:被调用目标方法在编译期可知,运行期保持不变。

晚期绑定:被调用目标方法在编译期不可知,只能在运行期根据实际类型确定

动态语言:判断变量值得类型信息。Pthon,js

静态语言:判断变量的类型信息。Java

引用了运行时常量池,运行时常量池在方法区

方法返回地址 return address

存放PC寄存器的值,及上层方法的下一次操作执行的指令值。用于恢复上层方法执行。

一些附加信息

可选,不一样有。例如:支持程序调试的信息。

本地方法栈 Native Method Stack

调用本地方法,native修饰的方法为本地方法。具体的虚拟机具体实现。会抛出异常

堆 Heap Area

生命周期:虚拟机启动时创建

占用空间:java堆是Java虚拟机管理的内存中最大的。可处于物理上不连续的内存空间,逻辑上连续即可,像磁盘。在开发中,可设置固定大小,也可以是可扩展的,不过主流的虚拟机都是按可扩展来实现。参数-Xms设置堆空间(新生代加老年代)的初始化内存大小,默认值物理内存/64,-Xmx 设置最大内存大小,默认值物理内存/4。建议开发中初始和最大设置为一样的值,避免频繁扩充影响性能

多线程:线程共享

存储:存放实例对象,几乎所有的对象实例都在这里分配内存(随着JIT编译器的发展和逃逸分析技术逐渐成熟,栈上分配、标量替换优化技术将会导致一些微妙的变化)

GC和Error:java堆是垃圾回收器管理的主要区域,也被称为“GC堆”。存在OOM

细分:从垃圾回收角度看,GC基本都采用分带收集算法,所以可分为新生代(Eden区、From Servivor区、To Servivor区)和老年代。从内存分配的角度看,还可能划分出多个线程私有的分配缓冲区(Thread Local Allocation Buffer,TLAB,参数-XX:UserTLAB默认开启,紧占Eden区的1%)。无论如何划分,都与存放无关,存储的仍是对象实例,划分目的是为了更好的回收,挥着更快的分配内存。

查看参数设置的方式

方式一:运行时参数设置-Xms600m -Xmx600m -XX:PrintGCDetails

方式二:cmd中运行指令jsp / jstat -gc 进程id

配置新生代老年代在堆得结构占比

-XX:NewRation=2,表示新生代占1,老年代占2,新生代占整个堆得1/3,默认值

-XX:NewRation=4,表示新生代占1,老年代占4,新生代占整个堆得1/5

可在命令行用【jinfo -flag NewRation 进程id】查看当前NewRation的值

新生代细分为ede:s1:s2=8:1:1 受自适应-XX:UseAddaptiveSizePolicy参数影响

要想实现此比例,需设置-XX:SurvivorRation=8

新对象分配过程(考虑内存分配和GC)

 阔值默认15(年龄计数器),可设置:-XX:MaxTenuringThreshold=<N>

YGC/Minor GC触发条件:新对象进来了,但是Eden区满了(S0/S1被动回收)。经过统计分析,80%的对象在新生代朝生夕死

触发Full GC的情况:

        (1)调用System.gc()

        (2)老年代空间不足:-XX:HandlePromotionFailure空间分配担保

        (3)方法区空间不足

补充:

-XX:+PrintFlagsInitial 查看所有的参数的默认值

-XX:+PrintFlagsInal 查看所有的参数的最终值(可能存在修改,不在是初始值)

-XX:MaxTenuringThreshold=<N> 设置新生代垃圾的最大年龄

-XX:HandlePromotionFailure是否设置空间分配担保

执行Minor GC判断老年代可连续使用空间是否大于Eden区所有对象总空间

大于:执行Minor GC是安全的的

小于:执行Full GC

-XX:+HandlePromotionFailure查看老年代可连续使用空间是否大于晋升到老年代的平均大小(大于:执行Minor GC 小于:执行Full GC)

-XX:-HandlePromotionFailure执行Full GC

-XX:+DoEscaprAnalysis 逃逸分析。默认开启

-XX:+EliminateAllocations 标量替换。默认开启

为什么有TLAB?堆空间是线程共享的,会出现线程安全问题,需要加锁,会影响分配速度。

逃逸分析(TaoBaoVM -> GCIH)?是否在方法外部被调用,被调用则存在逃逸。

逃逸分析进行代码优化?栈上分配、同步省略、标量替换

方法区 Method Area

栈、堆、方法区的交互关系

多线程:线程共享

存储:虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据

占用空间:第二大,参数设置JDK1.8前后有变化

GC和Error:有GC和异常OOM

方法区的演进

Jdk1.6及以前:有永久代(permanent generation),静态变量存放在永久代

JDK1.7 PermGenSpace:有永久代,但已经逐步‘去永久代’,字符串常量池、静态变量移除,保存在堆空间

JDK1.8及以后:无永久代,类型信息、字段、方法、常量保存在本地内存元空间,静态变量、字符串常量池仍在堆空间

空间设置变化

JDK1.7及以前:方法区的内存空间包含在虚拟机的内存中,可设置上限,比较容易出现OOM问题(-XX:PermSize=100m初始值,默认21m;-XX:MaxPermSize=100m最大值)

JDK1.8及以后 MetaSpace:元空间使用的是本地内存。-XX:Metaspacesize=100m(初始值,windows下默认21m);-XX:MaxMetaspacesize=100m(初始值,默认-1,无上限)

由于-XX:Metaspacesize会根据GC动态调整,建议设置大一些,避免多次Full GC 

永久代为什么要被元空间替换?

08年收购了JRockit进行融合,所以移除了永久代。

因为永久代大小很难确定。例如某个web工程中,因为功能点多,加载的类就很多,经常出现OOM。而元空间使用的是本地内存。

堆永久代调优比较困难,回收效率很低。方法区的垃圾回收主要针对常量池的回收和对类型的卸载,尤其是堆类型的卸载,条件相当的苛刻(实例和子类实例、类加载器、该类的Class对象没有引用才允许回收)。而元空间用本地内存,可有效减少Full GC。

为什么StringTable要调整?

因为在jdk1.7中,永久代只有在Full GC的时候才会回收。但是在实际开发当中会生成很多的字符串,回收效率低,导致永久代内存不足。放到堆里,能及时回收。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值