JVM02--运行数据区(内存结构)和参数调优

内存结构图如下:

                                       

类加载器负责加载class文件,执行引擎负责解释命令,提交操作系统执行。具体查看:

https://blog.csdn.net/tyf2007635/article/details/102964712

程序计数器(Program Counter Register)

程序计数器是一块较小的内存空间,它可以看作是当前线程所执行的字节码的行号指示器。

由于Java虚拟机的多线程是通过线程轮流切换并分配处理器执行时间的方式来实现的,一个处理器都只会执行一条线程中的指令。因此,为了线程切换后能恢复到正确的执行位置,每条线程都有一个独立的程序计数器,各个线程之间计数器互不影响,独立存储。称之为“线程私有”的内存。程序计数器内存区域是虚拟机中唯一没有规定OutOfMemoryError情况的区域。

如果执行一个Native方法,那这个计数器是空的。          

本地方法栈(Native Method Stack)

本地方法栈与虚拟机栈所发挥作用非常相似,它们之间的区别不过是虚拟机栈为虚拟机执行Java方法(也就是字节码)服务,而本地方法栈则为虚拟机使用到的native方法服务。本地方法栈也是抛出两个异常。

本地方法接口

本地接口的作用是融合不同的便曾语言为Java所用,它的初衷是融合C/C++程序,Java诞生出是C/C++横行的时候,想要立足必须有调用C/C++的方式,于是就在内存中专门开辟了一块区域处理标记为native的代码,它的具体做法是Native Method Stack中登记native方法,在执行引擎加载native libraies。

目前使用的越来越少,除非是与硬件有关的应用,比如通过Java驱动打印机或者管理生产设备,在企业应用总已经比较少见。因为可以使用Socket或者Web Service等。                          

方法区(Method Area)

方法区与java堆一样,是各个线程共享的内存区域,它用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。它有个别命叫Non-Heap(非堆)。当方法区无法满足内存分配需求时,抛出OutOfMemoryError异常。

很多开发者习惯将方法区称之为”永久代“,但严格本质上说两者不同。方法区只是jvm中定义的一种规范,在不同的虚拟机中实现的方式不一致,永久代是方法区(相当一个接口)的一个实现方式,在jdk1.7前已经原本放在永久代的字符串常量池移走。

在jdk1.8之前,jvm使用永久代实现方法区,而永久代存放在堆空间中。

从1.8开始,jvm使用元空间实现方法区,而元空间直接存放在服务器物理内存中。 

运行时常量池(Runtime Constant Pool)

运行时常量池是方法区的一部分。Class文件中除了有类的版本、字段、方法、接口等描述信息外,还有一项信息是常量池,用于存放编译期生成的各种字面量和符号引用,这部分内容将在加载后进入方法区的运行时常量池中存放。

Java堆(Java Heap)

java堆是java虚拟机所管理的内存中最大的一块,是被所有线程共享的一块内存区域,在虚拟机启动时创建。此内存区域的唯一目的就是存放对象实例,这一点在Java虚拟机规范中的描述是:所有的对象实例以及数组都要在堆上分配

根据Java虚拟机规范的规定,java堆可以处于物理上不连续的内存空间中。当前主流的虚拟机都是可扩展的(通过 -Xmx 和 -Xms 控制)。如果堆中没有内存完成实例分配,并且堆也无法再扩展时,将会抛出OutOfMemoryError异常。

堆结构

java堆是垃圾收集器管理的主要区域,因此也被成为“GC堆”(Garbage Collected Heap)。

从内存回收角度来看java堆可分为:新生代和老生代以及永久代。

默认的,新生代 ( Young ) 与老年代 ( Old ) 的比例的值为 1:2 ( 该值可以通过参数 –XX:NewRatio 来指定 ),即:新生代 ( Young ) = 1/3 的堆空间大小。老年代 ( Old ) = 2/3 的堆空间大小。其中,新生代 ( Young ) 被细分为 Eden 和 两个 Survivor 区域,这两个 Survivor 区域分别被命名为 from(s0) 和 to(s1),他们是两块大小相等并且可以互相角色的空间。

默认的,Edem : from : to = 8 : 1 : 1 ( 可以通过参数 –XX:SurvivorRatio 来设定 ),即: Eden = 8/10 的新生代空间大小,from = to = 1/10 的新生代空间大小

                                         

其中新生带存放新生的对象或者年龄不大的对象,老年代则存放老年对象。

绝大多数情况下,对象首先分配在eden区,在新生代回收后,如果对象还存活,则进入s0或s1区,之后每经过一次

新生代回收,如果对象存活则它的年龄就加1,对象达到一定的年龄后(15),则进入老年代。

从内存分配的角度看,线程共享的Java堆中可能划分出多个线程私有的分配缓冲区(Thread Local Allocation Buffer,TLAB)。无论怎么划分,都与存放内容无关,无论哪个区域,存储的都是对象实例,进一步的划分都是为了更好的回收内存,或者更快的分配内存。

永久代与元空间

jdk1.8之前jvm使用永久代实现方法区,从1.8开始,永久代已经被移除,被元空间取代。

元空间和永久代区别:永久代使用对内存,元空间直接使用本机物理内存,因此默认情况下元空间大小受本地内存限制。

堆异常

错误原因: java.lang.OutOfMemoryError: Java heap space 堆内存溢出

解决办法:设置堆内存大小 // -Xms1m -Xmx10m -XX:+PrintGCDetails -XX:+HeapDumpOnOutOfMemoryError

// -Xms1m -Xmx10m -XX:+PrintGCDetails -XX:+HeapDumpOnOutOfMemoryError
List<Object> listObject = new ArrayList<>();
for (int i = 0; i < 10; i++) {
    System.out.println("i:" + i);
    Byte[] bytes = new Byte[1 * 1024 * 1024];
    listObject.add(bytes);
}
System.out.println("添加成功...");

Java虚拟机栈(Java Virtual Machine Stacks)

栈也叫做栈内存,管理java程序的运行,有以下特点:

  • 它的生命周期和线程相同,在线程创建时创建的,线程结束栈内存也随着释放;
  • 生命周期和线程一致,是线程私有的;
  • 不存在垃圾回收问题;

帧栈存储数据

  • 本地变量(Local Variables):输入参数输出参数以及方法内的变量;
  • 栈操作:记录出栈、入栈的操作;
  • 栈帧数据:包括类文件、方法等;

运行原理

栈中的数据是以栈帧(Stack Frame)的格式存在,栈帧是一个内存区块,是一个数据集,是一个有关方法和运行期数据的数据集,当一个方法A被调用时产生一个栈帧F1,并被压入栈中,A方法有调用了B方法,于是产生栈帧F2也被压入栈,依此类对,待执行完毕后,先弹出F2栈帧,在弹出F1栈帧。

每个方法在执行的同时都会创建一个栈帧(Stack Frame)用于存储局部变量表、操作数栈、动态链接、方法出口等信息。

局部变量表存放了编译期可知的各种基本数据类型(8个基本数据类型)、对象引用(地址指针)、returnAddress类型。

局部变量表所需的内存空间在编译期间完成分配。在运行期间不会改变局部变量表的大小。栈的大小和具体的JVM实现有关,通常在256K-756K之间,约等于1Mb左右。

                                                      

异常

这个区域规定了两种异常状态:如果线程请求的栈深度大于虚拟机所允许的深度,则抛出StackOverflowError异常;如果虚拟机栈可以动态扩展,在扩展是无法申请到足够的内存,就会抛出OutOfMemoryError异常。

错误原因: java.lang.StackOverflowError 栈内存溢出

栈溢出 产生于递归调用,循环遍历是不会的,但是循环方法里面产生递归调用, 也会发生栈溢出。

解决办法:设置线程最大调用深度

-Xss5m 设置最大调用深度

public class JvmDemo04 {
	 private static int count;
	 public static void count(){
		try {
			 count++;
			 count(); 
		} catch (Throwable e) {
			System.out.println("最大深度:"+count);
			e.printStackTrace();
		}
	 }
	 public static void main(String[] args) {
		 count();
	}
}

栈和堆和方法区交互

                              

直接内存(Direct Memory)

直接内存不是虚拟机运行时数据区的一部分,也不是java虚拟机规范中定义的内存区域。但这部分区域也呗频繁使用,而且也可能导致OutOfMemoryError异常

在JDK1.4中新加入的NIO(New Input/Output)类,引入了一种基于通道(Channel)与缓冲区(Buffer)的I/O方式,它可以使用Native函数库直接分配堆外内存,然后通过一个存储在java堆中的DirectByteBuffer对象作为这块内存的引用进行操作。

内存溢出与内存泄漏区别

Java内存泄漏就是没有及时清理内存垃圾,导致系统无法再给你提供内存资源(内存资源耗尽);

而Java内存溢出就是你要求分配的内存超出了系统能给你的,系统不能满足需求,于是产生溢出。

内存溢出,这个好理解,说明存储空间不够大。就像倒水倒多了,从杯子上面溢出了来了一样。

内存泄漏,原理是,使用过的内存空间没有被及时释放,长时间占用内存,最终导致内存空间不足,而出现内存溢出。

举例:创建过多的静态变量和常量,无法回收,导致先出现内存泄漏,时间长了,内存不足出现内存溢出问题。

JVM参数调优配置

常见参数配置

-XX:+PrintGC 每次触发GC的时候打印相关日志

-XX:+UseSerialGC 串行回收

-XX:+PrintGCDetails 更详细的GC日志

-Xms 堆初始值,生产环境配置和-Xmx大小一致

-Xmx 堆最大可用值

-Xmn 新生代堆最大可用值

-XX:SurvivorRatio 用来设置新生代中eden空间和from/to空间的比例.

-XX:NewRatio 配置新生代与老年代占比 1:2, 新生代小GC触发频率低

含以-XX:SurvivorRatio=eden/from=den/to

总结:在实际工作中,我们可以直接将初始的堆大小与最大堆大小相等,

这样的好处是可以减少程序运行时垃圾回收次数,从而提高效率。

-XX:SurvivorRatio 用来设置新生代中eden空间和from/to空间的比例.

堆内存大小配置

使用示例: -Xmx20m -Xms5m

说明: 当下Java应用最大可用内存为20M, 初始内存为5M

设置新生代比例参数

使用示例:-Xms20m -Xmx20m -Xmn1m -XX:SurvivorRatio=2 -XX:+PrintGCDetails -XX:+UseSerialGC

说明:堆内存初始化值20m,堆内存最大值20m,新生代最大值可用1m,eden空间和from/to空间的比例为2/1

设置新生代与老年代比例参数

使用示例: -Xms20m -Xmx20m -XX:SurvivorRatio=2 -XX:+PrintGCDetails -XX:+UseSerialGC

-XX:NewRatio=2

说明:堆内存初始化值20m,堆内存最大值20m,新生代最大值可用1m,eden空间和from/to空间的比例为2/1

新生代和老年代的占比为1/2

 

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值