概述

Java虚拟机会自动管理内存,不容易出现内存泄漏和内存溢出问题。Java虚拟机会在执行过程中将管理的内存分为若干个不同的数据区域。

运行时数据区域

在jdk1.8之前的版本与1.8版本略有不同,在jdk1.8之前:

jdk1.8:

以上图片来源:https://github.com/LikFre/JavaGuide

线程共享区域:

    1.堆

    2.方法区

    3.直接内存(非运行时数据区)

线程私有区域:

    1.虚拟机栈

    2.本地方法栈

    3.程序计数器

线程私有区域:

1.虚拟机栈

  1.它的生命周期随着线程的创建而创建,随着线程的结束而死亡;

  2.描述的是java方法执行的内存模型,java虚拟机栈是由一个个栈帧组成,每个栈帧都有局部变量表、动态链接、操作数栈、方法出口信息。

  3.局部变量表主要存放可知的各种数据类型(8种基本数据类型变量,对象引用变量);

  4.Java虚拟机栈是线程私有的,每个线程都有自己的虚拟机栈;

  5.java虚拟机栈中主要存放的是一个个栈帧,每调用一次方法都会有对应的栈帧压入虚拟机栈,每一个方法执行结束后,都会有一个栈帧弹出。

    Java方法有两种返回方式:1、return;

                 2、抛出异常;

    这两种方式都会导致栈帧弹出;

  java虚拟机栈会出现两种异常:StackOverFlowError和OutOfMemoryError :

    1.StackOverFlowError:如果java虚拟机栈的内存大小不允许动态扩展,当虚拟机栈请求的内存超过栈的最大内存时就会出现StackOverFlowError异常;

    2.OutOfMemoryError:如果java虚拟机栈的内存大小允许动态扩展,当线程请求栈的内存已用完,且无法再动态扩展时,抛出OutOfMemoryError异常;

2.本地方法栈

  1.与java虚拟机栈的生命周期相同,都是线程私有,随着线程的创建而创建,线程的结束而死亡。

  2.描述的是Native方法执行的内存模型,也是由栈帧组成,每个栈帧用于存放本地方法的局部变量表,操作数栈、动态链接、方法出口信息;

  3.也会出现两种异常:StackOverFlowError和OutOfMemoryError

  在HotSpot虚拟机中,虚拟机栈与本地方法栈合二为一;

3.程序计数器

  1.程序计数器是一块较小的内存空间,字节码解释器通过改变计数器的值来选取下一条字节码指令。分支、跳转、循环、异常处理、线程恢复都需要依赖程序计数器;

  2.程序计数器也是线程私有的,每个线程都有自己的程序计数器;

  3.程序计数器主要有两个作用:

    1.字节码解释器通过改变程序计数器的值来顺序的执行字节码指令;如:顺序执行、循环、跳转等;

    2.在多线程环境下,程序计数器用于记录当前线程执行的位置,当CPU切换再次执行当前线程时从程序计数器记录的位置继续执行;

  注意:程序计数器是唯一一个不会出现OutOfMemoryError异常的内存区域,它的生命周期随着线程的创建而创建,线程的结束而死亡;

线程共享区域:

1.堆

  1.java虚拟机管理的最大的一块内存区域,是所有线程共享的内存区域,随着虚拟机的启动而创建,主要用于存放对象实例,几乎所有的对象实例和数组都在堆中存储。

  2.堆也是垃圾收集器主要管理的区域,因此也被称为GC堆(Garbage Collected Heap);从垃圾回收的角度:由于现在的收集器都采用分代收集算法,堆又被分为:新生代和老年代,进一步分为Eden空间、From  Survivor、ToSurvivor。进一步划分目的是为了更好的回收内存或者更好的分配内存。

  

  3.上图中,Eden、S0、S1是新生代,TenTired是老年代。大部分情况,一个对象实例创建后存储在Eden区域,经历过一次垃圾回收之后,如果对象还存活,对象的年龄加+1(由Eden区进入到Survivor区),进入S0或者S1,当对象的年龄达到一定的阈值(默认是15),这个对象才会进入老年代区域,对象的阈值可以通过参数(-XX:MaxTenuringThreshold)来设置;

2.方法区

  1.与java堆一样,是线程共享的内存区域,主要用于存储加载过的类信息、被final修饰的常量、静态变量以及即时编译器编译的代码,虽然java虚拟机规范把方法区列为堆的一个逻辑部分,但是它却有一个别名NON-Heap(非堆),可能就是为了和堆区分开。

  2.方法区也被称为永久代,方法区是java虚拟机中的一种规范,并没有去实现它,在不同的JVM方法区的实现也不同,永久代是HotSpot的概念,在HotSpot虚拟机中永久代是对方法区的一种实现,其他的虚拟机并没有永久代这一说法。

  3.在jdk1.8之前,方法区还没有被移除,通过两个参数可以设置永久代的大小:

    -XX:PermSize=N //方法区 (永久代) 初始大小

    -XX:MaxPermSize=N //方法区 (永久代) 最大大小,超过这个值将会抛出 OutOfMemoryError 异常

  垃圾收集行为在这个区域很少出现,但并不是数据进入方法区就一直存在

  4.jdk1.8版本,方法区(永久代)被移除,取而代之的时元空间,使用的是直接内存,可以通过以下两个参数设置

    -XX:MetaspaceSize=N //设置 Metaspace 的初始(和最小大小)

     -XX:MaxMetaspaceSize=N //设置 Metaspace 的最大大小

与方法区最大的的不同就是,如果不指定大小,随着更多的类创建,虚拟机可能会耗尽所有可用的系统内存  

   5.为什么要用元空间替换永久代?

  整个永久代有JVM设置的固定大小上限,无法进行调整,而元空间使用的是直接内存,只受本机可用内存大小的限制,如果不指定最大元空间大小,-XX:MaxMetaspaceSize默认大小是unlimited,只受到本机可用内存大小限制,如果不指定初始大小,JVM会动态的分配应用程序所需要的大小。这只是其中一个原因,有兴趣的可以去查阅资料。

3.运行时常量池

  1.是方法区的一部分,主要用来存放编译器生成的各种字面量和引用。字面量(文本字符串、基本数据类型的值、声明为final的常量值、其他)

  2.常量池的大小也受到方法区大小的限制,超过常量池的大小会报OutOfMemoryError异常。

  3.jdk1.7及以后JVM将运行时常量池从方法区移出,在堆中开辟了一块区域用来存放运行时常量池。

直接内存:

  不属于运行时数据区,也不是虚拟机规范中定义的内存区域,但是被频繁的使用,也有可能引起OutOfMemoryError异常。

  jdk1.4中新加入的NIO,使用一种基于通道(Channel)和缓存(Buffer)的方式,直接使用Native方法分配堆外内存,通过在java堆中一个DirectByteBuffer对象作为这块内存的引用来操作,避免了java堆与Native堆之间来回复制数据,提高了性能。

HotSpot虚拟机:

1.对象的创建:

Step1:类加载检查,虚拟机遇到一条new指令时,首先检查指令的参数是否能在常量池中定位到这个类的符号引用,然后检查符号引用代表的类是否被加载、解析和初始化过,没有则执行相应的类加载过程。

Step2:分配内存,类加载检查通过后,虚拟机为新生的对象分配内存,在类加载完成后已经确定了对象的大小,分配内存实际就是在堆中分配一块确定大小的内存,分配内存有两种方式:指针碰撞和空闲列表,

    分配内存的方式取决于java堆内存是否规整,java堆内存是否规整取决于GC收集算法是“标记-清理”还是“标记-整理”;

    1.指针碰撞:堆内存规整,使用过的内存全部整合到一边,没有使用的内存在另一边,中间有一个分界值指针,只需要将指针往没有使用的内存方向移动对象内存大小即可。GC收集器:Serial、ParNew;

    2.空闲列表:堆内存不规整,虚拟机会维护一个列表,列表中记录未使用的内存大小,选择一块足够的内存来为对象分配,更新列表的记录。GC收集器:CMS

     分配内存的并发问题:对象的创建是很频繁的,虚拟机要保证线程的安全性,采用两种方式:

    1.CAS+失败重试:CAS是乐观锁的一种方式,每次认为不会有冲突的去执行某项操作,遇到冲突一直重试,知道成功为止。虚拟机使用CAS加失败重试的方式来保证更新操作的原子性。

    2.TLAB:为每一个线程预先在Eden区分配一块内存,线程为对象分配内存时,首先在TLAB中分配,TLAB中内存不够时或者已经用尽时,再使用CAS上述方式。

Step3:初始化零值,内存分配完成后,需要将分配到的内存空间初始化为零值,这一步保证了对象在不初始化的情况下,对象的实例字段在java代码中可以不赋初始值就使用。

Step4:设置对象头,初始化零值完成之后,虚拟机要对对象进行必要的设置,列如:这个对象是哪个类的实例、如何才能找到对象的元数据、对象的哈希码、对象的GC分代年龄等信息,这些信息存储在对象头中。

Step5:执行init方法,在new出对象之后,把对象按照程序员的意愿进行初始化,执行init方法。

2.对象在内存中的布局:

对象在内存中的布局可分为三个区域:对象头、实例数据和对齐填充;

1.对象头分为两部分:一部分存储自身的运行时数据(哈希码、GC分代年龄等),另一部分类型指针,通过指针来确定这个对象是哪个类的实例

2.实例数据:是真正存储对象的有效信息,对象的各种类型的字段内容。

3.对齐填充:不是必然存在的只是起到占位作用。HotSpot虚拟机的自动内存管理系统要求对象的起始地址必须是8字节的整数倍,前两部分不足8字节的整数倍时,使用对齐填充补足。

3.对象的访问定位:

创建对象就是为了使用对象,通过在栈上存储的引用类型变量来操作堆上的具体类型变量,对象的访问方式由虚拟机决定,目前主流的有两种:①使用句柄 ②直接指针

1.使用句柄:java堆中会有一块内存作为句柄池,栈中的引用类型变量中保存的是句柄池的句柄,句柄池中有对象的实例数据和对象类型信息的地址,也就是引用变量访问句柄池,句柄池再访问对象。

2.直接指针:栈中的引用类型变量中直接保存的是对象的地址,可以直接访问。

使用句柄访问的好处是,变量中保存的是稳定的句柄池的地址,在对象被移动时改变句柄池中的地址即可,变量保存的地址不需要改变。

使用直接指针的好处是:速度快,节省了一次指针定位的时间。

内容补充:

String类与常量池

String对象的两种创建方式:

String str1 = "Hello" ;
String str2 = new String("Hello");
String str3 = new String("Hello");
System.out.println(str1 == str2);//false
System.out.println(str2 == str3);//false

str1.先检查常量池中有没有“Hello”,没有就在常量池中创建,人后str1指向常量池中“Hello”的地址,常量池中已经有的话,str1直接指向常量池中“Hello”的地址。

str2,在堆中重新创建一个新的对象

第一种方式:在常量池中拿对象,

第二种方式:直接在堆内存中创建新的对象

String类型的常量池有两种使用方式

        String str1 = new String("Hello" );
String str2 = str1.intern();
String str3 = "Hello";
System.out.println(str1 == str2);//true
System.out.println(str2 == str3);//false

上述代码中 str2和str3指向的都是字符串常量池中的“Hello”

第一种方式:使用双引号声明的String对象会直接存储在常量池中

第二种方式:使用String提供的intern方法,intern()方法是一个本地方法,它的作用是:如果当前字符串对象的字符串已经在常量池中,那么直接返回常量池中该字符串的引用,如果当前字符串对象的字符串内容不在常量池中,那么在常量池中创建一个字符串,返回该字符串的引用。

字符串拼接:

        String str1 = "hel";
String str2 = "lo";
String str3 = "hel"+"lo";
String str4 = str1 + str2;
String str5 = "hello";
System.out.println(str3 == str4);//false
System.out.println(str4 == str5);//false
System.out.println(str3 == str5);//true

str4是在堆上新建的对象,str3与str5都是常量池中的“hello”

8种基本类型的包装类与常量池

java基本类型的包装类中有六种实现的常量池技术:Byte、Short、Integer、Long、Character、Boolean,前五种默认使用[-128,127]的缓存数据,超出这个范围才会创建对象。Double和Float没有实现常量池技术。

Integer  i1 = 44; 默认使用Integer.valueOf(44),从而使用常量池中的数据,只有超过【-128,127】才会在对中创建对象。  

Integer i2 = new Integer(44);这种直接在堆中创建对象,

 Integer i1 = 40;
Integer i2 = 40;
Integer i3 = 0;
Integer i4 = new Integer(40);
Integer i5 = new Integer(40);
Integer i6 = new Integer(0); System.out.println("i1=i2 " + (i1 == i2));//true
System.out.println("i1=i2+i3 " + (i1 == i2 + i3));//true
System.out.println("i1=i4 " + (i1 == i4));//false
System.out.println("i4=i5 " + (i4 == i5));//false
System.out.println("i4=i5+i6 " + (i4 == i5 + i6));//true
System.out.println("40=i5+i6 " + (40 == i5 + i6));//true

i5+i6这个操作符“+”不适用于Integer对象,所以i5与i6自动拆箱,加之后就变为:i4=40,又因为i4是Integer对象,无法与int对象比较,i4自动拆箱为int值为40,所以相等

原文地址:https://github.com/LikFre/JavaGuide/blob/master/docs/java/jvm/Java%E5%86%85%E5%AD%98%E5%8C%BA%E5%9F%9F.md

Java虚拟机内存详解的更多相关文章

  1. Elasticsearch Java 虚拟机配置详解

    Elasticsearch对Java虚拟机进行了预先的配置.通常情况下,因为这些配置的选择还是很谨慎的,所以你不需要太关心,并且你能立刻使用ElasticSearch. 但是,当你监视ElasticS ...

  2. Elasticsearch Java虚拟机配置详解(转)

    引言: 今天,事情终于发生了.Java6(Mustang),是2006年早些时候出来的,至今仍然应用在众多生产环境中,现在终于走到了尽头.已经没有什么理由阻止迁移到Java7(Dolphin)上了. ...

  3. java堆内存详解

    http://www.importnew.com/14630.htmljava堆的特点<深入理解java虚拟机>是什么描述java堆的 Java堆(Java Heap)是java虚拟机所管 ...

  4. JVM(二)Java虚拟机组成详解

    导读:详细而深入的总结,是对知识"豁然开朗"之后的"刻骨铭心",想忘记都难. Java虚拟机(Java Virtual Machine)下文简称jvm,上一篇我 ...

  5. Java虚拟机组成详解

    导读:详细而深入的总结,是对知识“豁然开朗”之后的“刻骨铭心”,想忘记都难. Java虚拟机(Java Virtual Machine)下文简称jvm,上一篇我们对jvm有了大体的认识,进入本文之后我 ...

  6. JVM(Java虚拟机)详解(JDK7)

    1.Java内存区域 运行时数据区域: Java 虚拟机在执行Java程序时,定义了若干种程序运行期间会使用到的运行时数据区,其中有一些会随着虚拟机启动而创建,随着虚拟机退出而销毁.另外一些则是与线程 ...

  7. Java虚拟机JVM详解

    一.JVM内存管理 1.1JVM运行时数据区 1.1.1程序计数器:记录当前线程正在执行的字节码指定的地址(行号) 为什么需要它:程序容易被打断 1.1.2虚拟机栈:存储当前线程运行方法时所需要的数据 ...

  8. Java虚拟机内存分配详解

    简介 了解Java虚拟机内存分布的好处 1.了解Java内存管理的细节,有助于程序员编写出性能更好的程序.比如,在新的线程创建时,JVM会为每个线程创建一个专属的栈 (stack),其栈是先进后出的数 ...

  9. Java中堆内存和栈内存详解2

    Java中堆内存和栈内存详解   Java把内存分成两种,一种叫做栈内存,一种叫做堆内存 在函数中定义的一些基本类型的变量和对象的引用变量都是在函数的栈内存中分配.当在一段代码块中定义一个变量时,ja ...

随机推荐

  1. Ubuntu 16.04下SecureCRT无法输入中文的解决思路

    说明:首先网上的方法基本都是不行的,别试了. 但是可以有弥补方案: 1.通过外界的软件编辑好中文,然后粘贴过去.虽然是多了一步,但是也可以输入中文. 2.关于这个问题应该是没有中文字体库导致的,可以尝 ...

  2. linux工具一览

    http://linuxtools-rst.readthedocs.io/zh_CN/latest/tool/sar.html

  3. ThinkPHP3.2 点击看不清刷新验证码

    欢迎使用Markdown编辑器写博客 baidu了一下.发现没有可用的源码,自己想了想,以下的方法可行. <!DOCTYPE html> <html> <head> ...

  4. SaltStack学习系列之Nginx部署

    目录结构 |-- nginx | |-- files #放包文件的 | | |-- admin_22.conf | | |-- fastcgi_params | | |-- jim_fix_param ...

  5. Erlang 又生虫了

    好久不玩Erlang了.近期想鼓捣Eresye,下了个最新版OTP 17,结果.发现了问题. 安装这个最新版的Erlang (erl 6.0)后,用erlc编译了Eresye 1.2.5,并放入其li ...

  6. powerShell赋权限

    1.给网站赋权限 Set-SPUser –Identity “用户名” –AddPermissionLevel “参与讨论” –web “http://url” 2.给列表赋权限 $web = Get ...

  7. org.apache.commons.fileupload.FileUploadBase$SizeLimitExceededException: the request was rejected be

    1.错误描写叙述 八月 14, 2015 3:03:05 下午 com.opensymphony.xwork2.util.logging.jdk.JdkLogger warn 警告: Request ...

  8. A program to print Fahrenheit-Celsius table with floating-point values

    我的主力博客:半亩方塘 Another program to print Fahrenheit-Celsius table with decimal integer This program is p ...

  9. 在不同的系统中的virtualbox中安装Ubuntu SDK

    对非常多的开发人员来说.你们可能使用的不是Ubuntu操作系统.在这样的情况下,开发人员须要在自己的操作系统中(OS X及Windows)安装virtualbox,并在VirtualBox中安装Ubu ...

  10. redis集群在window下安装

    1.下载安装单机版:  https://github.com/MSOpenTech/redis/releases/download/win-3.2.100/Redis-x64-3.2.100.msi ...