Java-JVM 虚拟机原理调优实战

一、基础

栈帧(Stack Frame)栈空间的 基本元素,用于 方法的调用和方法的执行的数据结构

堆内存用来存放由new创建的对象和数组。在堆中分配的内存,由Java虚拟机的自动垃圾回收器来管理。在堆中产生了一个数组或对象后,还可以在栈中定义一个特殊的变量,让栈中这个变量的取值等于数组或对象在堆内存中的首地址,栈中的这个变量就成了数组或对象的引用变量。引用变量就相当于是为数组或对象起的一个名称,以后就可以在程序中使用栈中的引用变量来访问堆中的数组或对象。

String是一个特殊的包装类数据。可以用: String str = new String("abc");String str = "abc";两种的形式来创建,第一种是用new()来新建对象的,它会在存放于堆中。每调用一次就会创建一个新的对象。而第二种是先在栈中创建一个对String类的对象引用变量str,然后查找栈中有没有存放"abc",如果没有,则将"abc"存放进栈,并令str指向"abc",如果已经有"abc" 则直接令str指向"abc"。比较类里面的数值是否相等时,用equals()方法;

程序计数器:指向当前线程所指向的字节码指令的(地址)行号。

本地方法栈:存放方法名有native修饰,public static native void sleep(long millis) throws InterruptedException

栈帧和方法的关系:每一个方法的执行都对应了一个栈帧入栈到出栈的过程。

什么时候分配栈帧的内存?分配多少内存?

在编译代码的时候栈帧中需要多大的局部变量表,多深的操作数据栈都已经完全确定了,因此一个栈帧需要分配多少内存,不会受程序运行期数据的影响,只取决于虚拟机的实现。

二、图解

2.1 栈帧详解

2.1.1 局部变量表(Local variable Table): 主要关注的栈内存,就是JVM栈中的局部变量表的部分。

局部变量表(Local variable Table)是一组储值空间,用于存放方法参数和方法的内部定义的局部变量,并且在Java编译为.class文件的时候就分配了该方法所需要的局部变量表的最大容量.

2.1.2 变量槽(Variable Slot)

是局部变量表容量的最小单位 4字节 32 位长度(4* 8) ;

blloean ,byte ,char short,int float ,【refrence】,double 和 long 8字节型需要2个Slot空间.

【reference】引用地址:一般来说虚拟机都能从直接引用或者间接引用中查找对象一下2点

在堆区存放的数据的开始索引

数据类型在方法区的数据类型

2.1.3 实例

方法执行时候,虚拟机使用局部变量表完成参数的传递,如果执行的方法是实例对象的方法,局部变量的0索引(比如x00001)就是在堆区对象实例的引用(通过this可以访问到这个地址)

其他参数按照顺序排列。

2.1.4 Slot复用

为了节省空间,Slot是可以复用的,也就是PC计数器的指令指已经超出了某个变量的作用域,(执行完毕)那么这个变量对应的Slot就会给其他变量使用,

优点:节省栈帧空间

缺点:影响垃圾回收:如果有大方法占用比较多的Slot,然后又不及时清除,或者设置为null,垃圾回收器就不能回收该内存.

2.1.5 动态连接(Dynamic Link)每个帧都包含一个指向运行时常量池中该帧所属于方法的引用,持有这个引用是为了支付方法调用过程中的动态连接。

静态解析:在类的加载的阶段的解析2.3阶段会将符号(PI)转化为直接引用(0x001),这种转化也成为静态解析。

动态连接:另外一部分,在每次运行的时候将符号转化为直接引用,这部分称之为动态连接

2.1.6 方法返回地址(父帧)1.执行方法返回的2种方法:

1.正常退出:执行到return ,就退出方法2.异常退出:发生异常且未处理,

2.无论采用 哪一种方法,在退出后都需要返回之前方法调用的位置,就是父帧

一般来说,方法正常退出时候,PC计数器的值可以作为放回的地址,栈帧中会保存这个计数器中的值,但是方法异常退出时候,返回的地址通过异常处理器表来确定的,栈帧中一般不会保存这部分的信息。

2.1.7 操作数栈:

操作数栈 和 局部变量表一致

编译为class就确定大小

Slot为基础储存单位

作用:当一个方法开始时候,方法数栈始空的,执行中,各种字节码文件指令往操作数栈中读取内容和写入内容,入栈和出栈的操作

比如算术运算就是通过操作数栈来进行的,或者调用其他方法时候是用操作数栈来传递参数的

三、 栈溢出模拟 SO(StackOverflowError)

public class Demo {

public static void main(String[] args) {

new Demo().a();

}

private void a(){

b();

}

private void b(){

a();

}

}

调试运行栈帧截图:

方法每次调用都会新增一个栈帧,a() 方法和 b()循环调用导致栈内存暴满。

方法的递归调用同理

四、堆Heap

新生区:

新生区是类的诞生、成长、消亡的区域,

一个类在这里产生,应用,最后被垃圾回收器收集,结束生命。

新生区又分为两部分:伊甸区(Eden Space)和幸存者区(Survivor space),所有的类都是在伊甸区被new出来的。

幸村区有两个:0区(Survivor 0 space)和1区(Survivor 1space)。

当伊甸园的空间用完时,程序又需要创建对象,Jvm的垃圾回收器将对伊甸园区进行垃圾回收(Minor GC),将伊甸园区中的不再被其他对象所引用的对象进行销毁。然后将伊甸园区中的剩余对象移动到幸存0区。若幸存0区也满了,再对该区进行垃圾回收,然后移动到1区。

那如果1区也满了呢?再移动到养老区。若养老区也满了,那么这个时候将产生MajorGC(FullGC),进行养老区的内存清理。若养老区执行了Full GC之后发现依然无法进行对象的保存,就会产生OOM异常"OutOfMemoryError"。

如果出现java.lang.OutOfMemoryError:Java heap space异常,说明Java虚拟机的对内存不够。原因有二:

(1)Java虚拟机的堆内存设置不够,可以通过参数-Xms、Xmx来调整。

(2)代码中创建了大量大对象,并且长时间不能被垃圾收集器收集(存在被引用)

Java堆从GC的角度还可以细分为:新生代(Eden区、From Survivor区和To Survivor区)和老年代。

MinorGC的过程(复制->清空->互换),其中,Eden:From:To = 8:1:1

1:eden、SurvivorFrom复制到survivorTo,年龄+1  首先,把Eden和SurvivorFrom区域中存活的对象复制到SurvivorTo区域(如果有对象的年龄以及达到了老年的标准,则赋值到老年代区),同时把这些对象的年龄+1(如果SurvivorTo不够位置了就放到老年区)

2:清空eden、SurvivorFrom  然后,清空Eden和SurvivorFrom中的对象

3:SurvivorTo和SurvivorFrom互换  最后,SurvivorTo和SurvivorFrom互换,原SurvivorTo成为下一次GC时SurvivorFrom区

Java1.8之后将最初的永久代取消了,由元空间取代。

在Java8中,永久代已经被移除了。被一个称为元空间的区域所取代。元空间的本质和永久代类似。

元空间与永久代之间最大的区别在于:

元空间并不在虚拟机中而是使用本地内存。因此,默认情况下,元空间的大小仅受本地内存限制。类的元数据当如native memeory,字符串池和类的静态变量放入java堆中,这样可以加载多少类的元数据就不在由MaxPermSize控制,而由系统的实际可用空间来控制。

通过下面实例可以观察Heap 中新生区[eden->surviorFrom0->surviorFrom1]->老年代区[old] 内存变化到内存报错

报错:Exception in thread "main" java.lang.OutOfMemoryError: Java heap space

public class HeapOom {

private byte[] b = new byte[1024];

public static void main(String[] args) throws InterruptedException {
    ArrayList<HeapOom> list = new ArrayList<>();
    while(true){
        list.add(new HeapOom());
        Thread.sleep(10);
    }
}

}

堆内存空间调整参数

参数名称 描述

-Xms 设置初始分配大小,默认为物理内存的1/64

-Xmx 最大分配内存,默认为物理内存的1/4

-XX:+PrintGCDetails 输出详细的GC处理日志

-XX:+PrintGCTimeStamps 输出GC的时间戳信息

-XX:+PrintGCDateStamps 输出GC的时间戳信息(以日期的形式,如2019-09-15T16:24:24.155+0800)

-XX:+PrintHeapAtGC 在GC进行处理的前后打印堆内存信息

-Xloggc:保存路径 设置日志信息保存文件

相关推荐
重生之我在20年代敲代码1 分钟前
strncpy函数的使用和模拟实现
c语言·开发语言·c++·经验分享·笔记
爱上语文2 分钟前
Springboot的三层架构
java·开发语言·spring boot·后端·spring
serve the people6 分钟前
springboot 单独新建一个文件实时写数据,当文件大于100M时按照日期时间做文件名进行归档
java·spring boot·后端
longlongqin8 分钟前
JVM 内存结构?
jvm
qmx_071 小时前
HTB-Jerry(tomcat war文件、msfvenom)
java·web安全·网络安全·tomcat
为风而战1 小时前
IIS+Ngnix+Tomcat 部署网站 用IIS实现反向代理
java·tomcat
编程零零七2 小时前
Python数据分析工具(三):pymssql的用法
开发语言·前端·数据库·python·oracle·数据分析·pymssql
技术无疆3 小时前
快速开发与维护:探索 AndroidAnnotations
android·java·android studio·android-studio·androidx·代码注入
2401_858286113 小时前
52.【C语言】 字符函数和字符串函数(strcat函数)
c语言·开发语言
铁松溜达py3 小时前
编译器/工具链环境:GCC vs LLVM/Clang,MSVCRT vs UCRT
开发语言·网络