JVM内存模型

发表时间:2017-07-19 21:41:21 浏览量( 33 ) 留言数( 0 )

学习目标:

1、了解JVM的内存模型


学习过程:

一、JVM虚拟机介绍   

 Java应用程序是运行在JVM上面的,正式有了JVM,Java应用程序才能跨平台运行,平时我们都是直接使用Oracle/SUN JDK下面的JVM的,事实上除了这个之外也还有其他的虚拟机的。主流选择有:

  • HotSpot VM

  • J9 VM

  • Zing VM

   HotSpot VM是绝对的主流。平时大家如果没有特别的说明,就是使用HotSpot。JDK8的HotSpot VM已经是以前的HotSpot VM与JRockit VM的合并版,也就是“HotRockit”,只是产品里名字还是叫HotSpot VM。

    这个合并并不是要把JRockit的部分代码插进HotSpot里,而是把前者一些有价值的功能在后者里重新实现一遍。移除PermGen、Java Flight Recorder、jcmd等都属于合并项目的一部分。

    J9是IBM开发的一个高度模块化的JVM。在许多平台上,IBM J9 VM都只能跟IBM产品一起使用。这不是技术限制,而是许可证限制。例如说在Windows上IBM JDK不是免费公开的,而是要跟IBM其它产品一起捆绑发布的;使用IBM Rational、IBM WebSphere的话都有机会用到J9 VM(也可以自己选择配置使用别的Java SE JVM)。

    Zing VM是一个从Sun HoSpot VM fork出来的一个高性能JVM,可以运行在Linux/x86-64平台上。Azul为它重新写了一套GC,也修改了VM内的许多实现细节在要求低延迟、快速预热等的场景里,Zing VM都会比HotSpot VM表现更好。

    还有其他的一些JRockit等等的VM,大家可以自己去了解一下,我们后面的可能都只会用到HotSpot。

二、JVM的内存模型

   Java程序在运行时,需要在内存中的分配空间。为了提高运算效率,就对数据进行了不同空间的划分,因为每一片区域都有特定的处理数据方式和内存管理方式。具体划分为如下几个个内存空间(不同的版本回有所不同,比如JDK 8以后就不一样了):

  • 栈:存放每一个线程的局部变量,是线程私有的内存空间。

  • 堆:最大的一块内存区域,存放所有new出来的东西。

  • 方法区:被虚拟机加载的类信息、常量、静态常量等。通常和永久区(Perm)关联在一起  JDK8以后就没有了,但是JDK7的时候需要注意,因为现在反射的技术用得多,吃不是也会不够的,但是已办也不需要设置得很大,够用即可

  • 程序计数器:存放下一条要运行的命令的地址(和系统相关)。

   我们也可以简单的划分为:堆和非堆,堆内存(Heap Memory)是在 Java虚拟机启动时创建,非堆内存(Non-heap Memory)是在JVM堆之外的内存。简单来说,堆是Java代码可及的内存,留给开发人员使用的;非堆是JVM留给自己用的,包含方法区、JVM内部处理或优化所需的内存(如 JITCompiler,Just-in-time Compiler,即时编译后的代码缓存)、每个类结构(如运行时常数池、字段和方法数据)以及方法和构造方法的代码。

   我们需要重点关注的就是栈、堆和方法区,其中需要经常调整是堆内存。

一、栈

    注意这个栈和数据结构中的stack有相似之处,但并不是用户态的。准确的讲它压入的每个栈帧(Stack Frame)是程序指令以及局部变量表,每个方法调用对应一个栈帧。局部变量表包括各种基本数据类型:boolean、byte、char、short、int、float、long、double以及对象的引用。我们需要注意到每个线程都有独立的栈并且是互相隔离的。

    栈的大小可以受到几个因素影响,一个是jvm参数 -XSS,默认值随着虚拟机版本以及操作系统影响,与栈相关的异常有:java.lang.StackOverflowError和OutOfMemoryError,如果栈的深度大于可用的深度,那么抛出StackOverflowError,如果栈的内存在动态扩展时内存不够就会抛出OutOfMemoryError。我们可以通过设置线程栈大小(-Xss)来改变栈的大小。

看一下下面这个例子。

public class TestStack {
	
	private int count=0;

	
	public void method() {

		count++;
		method();
	}

	@Test
	public void testMethod() {
		try {
			method();
		}catch (Throwable e) {
			System.out.println("count="+count);
			e.printStackTrace();
		}
		
	}
	
}

如果使用java命令行运行可以直接在后面添加这些-Xss信息,如果时使用eclipse调试运行,可以参考下图

attcontent/a01b6ce3-fce6-40a0-98af-9b46e7245ed8.png

当我们设置为256K时,只能有2723的深度

count=2723

java.lang.StackOverflowError

我们可以调整成为1M -Xss1M

count=20821

java.lang.StackOverflowError

栈深度马上就大了。

除此之外,栈帧得大小也是会影响得。和整个JVM的配置也是有关系的,已办我们可以这样认为:

线程数 = (系统空闲内存-堆内存(-Xms, -Xmx)- perm区内存(-XX:MaxPermSize)) / 线程栈大小(-Xss)

public class TestStack {
	
	private int count=0;
	private long age;
	private double test;
	private double abc;
	
	public void method(long age,double test,double abc) {
		age=1L;
		test=34D;
		abc=234D;
		
		count++;
		method(1L,23.5,34.6);
	}

	@Test
	public void testMethod() {
		try {
			method(1L,23.5,34.6);
		}catch (Throwable e) {
			System.out.println("count="+count);
			e.printStackTrace();
		}
		
	}
	
}

我们可以在内多设置几个变量,深度也会发生变化的。

count=2199

java.lang.StackOverflowError

   使用命令 jstack <pid>可以列出当前pid对应jvm的所有线程栈描述,描述主要包括了每个线程的状态以及堆栈内各栈帧的方法全限定名,代码位置。注意这只是为了可阅读性,并不是说栈里存着的就是这些字符串。

   一般来说我们并不需要设置-Xss的,使用默认的大小即可,如果一个栈的大小经常不过,应该多从程序代码下功夫改进,而不是通过调整-Xss来设置。

二.堆

    对于大多数应用来说,Java 堆(Java Heap)是Java 虚拟机所管理的内存中最大的一块。Java 堆是被所有线程共享的一块内存区域,在虚拟机启动时创建。此内存区域的唯一目的就是存放对象实例,几乎所有的对象实例都在这里分配内存。

    首先堆可以划分为新生代和老年代,新生代用于报错刚刚产生的新对象,而老年代时新生代的对象经过多次垃圾回收后还存活的,就会移动老年代。新生代又可以划分为Eden、s0、s1,有关堆的设置比较多

-Xmx3550m:设置JVM最大可用堆内存为3550M。

-Xms3550m:设置JVM初始堆内存为3550m。此值可以设置与-Xmx相同,以避免每次垃圾回收完成后JVM重新分配内存。

-Xmn2g:设置年轻代大小为2G。整个JVM内存大小=年轻代大小 + 年老代大小 + 持久代大小。

-XX:NewRatio=4:设置年轻代(包括Eden和两个Survivor区)与年老代的比值(除去持久代)。使用比例就可以不需要设置Xmn,如果设置为4,则年轻代与年老代所占比值为1:4,年轻代占整个堆栈的1/5

-XX:SurvivorRatio=4:设置年轻代中Eden区与Survivor区的大小比值。设置为4,则两个Survivor区与一个Eden区的比值为2:4,一个Survivor区占整个年轻代的1/6

-XX:MaxTenuringThreshold=15:设置垃圾最大年龄。如果设置为0的话,则年轻代对象不经过Survivor区,直接进入年老代。对于年老代比较多的应用,可以提高效率。如果将此值设置为一个较大值,则年轻代对象会在Survivor区进行多次复制,这样可以增加对象在年轻代的存活时间,增加在年轻代被回收的概率。


attcontent/1317ad05-946c-4573-ba59-07965402bf7e.png

   我们以后工作的时候经常会调整堆的相关参数,也是

三、静态方法区

    最后讲一讲静态方法区,又称为永久代(Perm Generation)。它用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。

常见的JVM配置包括:

-XX:MaxPermSize=512m