JVM_1

喜夏-厌秋 提交于 2019-12-01 05:12:42

这里面加了很多我自己的看法,有啥错误的阔以给我留言一手,多谢您!

JAVA虚拟机在执行java程序的时候会把它所管理的内存划分为若干个不同的数据区域,这些区域都有各自不同的用途,以及创建和销毁时间.

java虚拟机所管理的内存包括以下几个运行时数据区域

Method Area方法区(线程共享)

VM Stack虚拟机栈(线程私有)

Native Method Stack本地方法栈(线程私有)

Heap堆(线程共享)

Program Counter Register程序计数器(线程私有)

注意:这几个区域都是JVM管理的运行时数据区,要记住运行时数据区这几个字.

程序计数器(线程私有)

它是一块较小的内存空间,可以看作是当前线程所执行的字节码的行号指示器,其中的值的数据类型为returnAddress类型

字节码解释器工作的时候就是通过改变这个计数器的值来选取下一条需要执行的字节码指令的,分支,循环,跳转,异常处理,线程恢复等都需要依赖这个计数器来完成.

为什么说它是线程私有的呢?

  • 因为虚拟机的多线程是通过线程轮流切换并分配处理器时间的方式来实现的,在任何一个确定的时刻,一个处理器都只会执行一条线程中的指令,因此,为了保证线程切换后能恢复到正确的执行位置,每条线程都会有一个独立的程序计数器,各条线程之间的计数器互不影响,独立存储!

如果当前线程执行的时java方法,这个计数器记录的就是正在执行的虚拟机字节码指令的地址;而如果是Native方法那这个计数器则为空.

  • native方法就是一个用java代码调用的用非java语言编写的接口(接口名就是方法名),定义一个native方法的时候,是没有实现体的,而且不能有实现体,有实现体就会报错,因为它的实现体是由非java代码例如C在外面实现的.
    • 例子如下:
      1234567
      public class IHaveNatives {    native public void Native1( int x ) ;   native static public long Native2() ;    native synchronized private float Native3( Object o ) ;   native void Native4( int[] ary ) throws Exception ;    }

注意:此内存区域是唯一一个不会出现OutOfMemoryError错误的区域!!!

虚拟机栈(线程私有)

虚拟机栈的生命周期与线程相同,也就是说一个虚拟机栈实例只负责一个线程,当线程结束以后,这个虚拟机栈实例也就结束了,跟递归的过程中会创建多个栈一样的

虚拟机栈描述了java方法执行时的内存模型

  • 每个方法在执行时都会创建一个东西,叫做栈帧,这个栈帧只属于这一个方法,栈帧里面放着局部变量表的信息,方法出口方法里的信息,每一个方法调用直至执行结束的过程就对应着栈帧入栈直至出栈的过程.
    • 栈帧里面有一个很重要的部分,叫做局部变量表,也就是我们当通常把java内存分为栈和堆时里面的栈,局部变量表部分存储着编译期可知的各种基本数据类型数据,对象引用(是对象引用哈,不是对象实例)以及returnAddress类型数据(指向了一条字节码指令的地址,这个不用管,知道就行,跟java代码没有直接关系) .
  • 注意:
    • long和double这两个64位类型的数据会占用2个局部变量空间,其余数据类型都只占用一个局部变量空间.

    • 局部变量表所需的内存空间在编译期间完成分配,在运行期间不会改变其大小.

在java的虚拟机规范中,这个区域只有两种异常状况:

  • StackOverFlowError异常:这个异常的产生是因为线程请求的深度大于虚拟机(不是虚拟机栈)所允许的深度,就会抛出这个异常。

  • OutOfMemory异常:这个异常产生的原因是,虽然大部分虚拟机栈可以动态扩展,但是当扩展时无法申请到足够的内存时就会抛出这个异常.

实在不理解就记住栈中放的是运行期间对象引用和基本类型数据就成哈哈哈哈,简单暴力.

本地方法栈(线程私有)

本地方法栈与虚拟机栈所发挥的作用是非常相似的,他们之间的区别不过是虚拟机栈为虚拟机执行java方法(也就是字节码文件),而本地方法栈则为虚拟机中使用到的native方法服务,与虚拟机栈一样,它也会抛出StackOverFlowError异常OutOfMemory异常.

堆(线程共享)

java堆是java虚拟机管理的内存中最大的一块,在虚拟机启动时创建,它的唯一作用就是存放对象实例,就是new处理的都在堆里,即所有的对象实例以及数组都要在堆上分配内存空间.

java堆是垃圾收集器管理的主要区域,因此很多时候被称为GC堆,垃圾回收算法我写在下一篇博客.

java堆可以处于物理上不连续的内存空间中,只要逻辑上是连续的即可,就像我们的磁盘空间一样,在实现的时候,既可以是固定大小的,也可以是可扩展的比如加固态,书上说当前主流的虚拟机都是按照可扩展来实现的(通过-Xmx设置最大值和-Xms设置最小值).

他们博客说java的垃圾回收器在内存使用达到-Xms才开始回收.

如果在堆内存中没有完成实例分配,并且堆也不可再扩展时,将会抛出OutOfMemoryError异常.

JDK1.7以后字符串常量池(String s = new String(“str”)中”str”就放在字符串常量池中)就从方法区拿到了堆里

方法区(线程共享)

有的博客说方法区里面放着的都是唯一存在的东西,比如class文件和常量,静态常量这些都是唯一的嘛.

方法区用于存储已被虚拟机加载的类信息(比如全局变量的引用,(父类)类的全名称(包.类名),类的修饰符,访问权限等),常量,静态常量等数据,我觉得方法最开始也是存在于方法区中的,运行的时候才放到栈中去.

  • 理解:
    • 这里的常量指的是final修饰的常量编译时期的字符串常量(即String s = “abc”中的”abc”).
      • 这里要区别一下字符串常量池中的常量,我看他们的博客说的字符串常量池中存放的也是常量,不过只有例如String s = new String("abc"),这样子创建对象时生成的常量”abc”才会放到字符串常量池中去,不过也不是没有道理啊,只有new的时候才会去堆分配空间嘛,编译期又不会去堆里搞事情对吧哈哈哈.
    • 静态常量就是static final修饰的数据.

    • 这里的类信息是指: 类的版本、字段、方法、接口,全局变量等信息.

在java虚拟机规范中将方法区描述为堆的一个逻辑部分,但它却有一个别名叫做Non-Heap,目的就是为了和java堆区分开了.

方法区和堆一样也不需要一定是连续的,是可扩展的.

当方法区无法满足内存分配的需求时,也会报OutOfMemoryError异常.

运行时常量池

.java文件被编译后,就生成了.class字节码文件,class文件中存储了类的版本、字段、方法、接口等信息,以及常量池,这里的常量池中存放编译期生成的字面量符号引用.

字面量和符号引用的概念

  • 字面量可以理解为就是值本身,比如字符串的值,基本类型的值,和final常量值.

  • 符号引用就是以一组符号来描述所引用的目标,符号可以是任何形式的字面量,只要使用时能够无歧义的定位到目标即可,类加载的时候第一次加载到这个符号时,就会将这个符号引用解析成直接引用(目标的地址).

    • 可以这么理解: 符号引用就是一个类中(当然不仅是类,还包括类的其他部分,比如方法,字段等),引入了其他的类,可是JVM并不知道引入的其他类在哪里,所以就用唯一符号来代替,等到类加载器去解析的时候,就把符号引用找到那个引用类的地址,这个地址也就是直接引用。

运行时常量池是方法区的一部分,它的作用就是当类加载后进入方法区后,接受.class文件中的常量池中的内容,但是这并不是只放编译期的,它具有动态性,运行期间的也可以将字面量和符号引用放进去,但是这时放进去只能通过对象调用intern()方法,在JDK7及其以后(跟JDK6不一样),它只是将在字符串常量池的对象的引用放进去,注意本来对象的值是放在堆中的字符串常量池中的,这时候只是将引用放到了运行时常量池,但是字符串常量池中还在.

既然运行时常量池是方法区的一部分,当常量池无法申请到内存时就会报错OutOfMemory异常.

String类的intern()方法(native方法,其设计初衷就是为了重用String对象,节省内存消耗)

找到了一篇大佬的博客,看完他的博客我懂了,他里面没有细分字符串常量池和运行时常量池,大家看完我上面的博客再去看它的,注意一下区分两个常量池就可以了,博客地址: https://blog.csdn.net/seu_calvin/article/details/52291082/

大家看完之后,再回来,我在这里用大佬的例子在这里区分一下运行时常量池和字符串常量池:

12345678910111213141516171819202122232425262728293031
String str1 = new String("SEU")+ new String("Calvin");这一行代码在堆中生成了3个对象,一个值为"SEU"的String对象,一个值为"Calvin"的对象,一个值为  "SEUCalvin"的对象最后str1指向的是值为"SEUCalvin"的对象这么理解:刚开始这三个对象都在堆中,但是他们的值都在字符串常量池,是字符串常量池!!!!!!System.out.println(str1.intern() == str1);执行`str1.intern()`方法后,按照我上面说的,它先去看运行时常量池里面有没有"SEUCalvin"这个值没有它就去堆中的字符串常量池中去找,诶,字符串常量池里有它就把字符串常量池中"SEUCalvin"这个值的引用放到运行时常量池!!!!!是运行时常量池,然后返回这个引用也就是说此时`str1.intern()`返回的是str1的值在堆中的地址!!!str1本来指向的就是堆中的那个值,那他俩的地址是不是就相等了.System.out.println(str1 == "SEUCalvin");这里str1指向的是堆中的"SEUCalvin"的地址,而直接写"SEUCalvin"他就会去运行时常量池中找有没有这个值,发现里面有,就直接返回其地址,就是堆中的"SEUCalvin"的地址,所以返回trueString str2 = "SEUCalvin";//相对于上面的代码新加的一行代码,其余不变这里首先去看运行时常量池里有没有"SEUCalvin"这个值,发现没有,就直接在运行时常量池里创建String str1 = new String("SEU")+ new String("Calvin");这里和上面一样,他会创建三个对象在堆中,str1指向的是"SEUCalvin"在字符串常量池中的地址!!!!System.out.println(str1.intern() == str1); //false这里的str1.intern()它先去运行时常量池里面找有没有"SEUCalvin",发现,诶,有,就直接返回了注意这里返回的是"SEUCalvin"在运行时常量池中的地址!!!!而str1指向的是字符串常量池中的地址,你说相等不相等嘛System.out.println(str1 == "SEUCalvin");  //false这里也一样,str1是字符串常量池中的地址,而"SEUCalvin"是运行时常量池中的地址

原文:大专栏  JVM_1


易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!