(一)java的内存模型

 

 程序计数器(私有)

  程序计数器:"是一个非常小的内存空间,用来保证程序依次执行",它可以看作是当前线程所执行的字节码的行号指示器 由于java虚拟机的多线程是通过线程轮流切换并分配处理器执行时间的方式来实现的,在切出后切回的时候需要一个标识。
(私有) "基本类型、运算、方法服务、指向堆内存的指针"
  虚拟机栈('执行的是java方法服务') 它的生命周期与线程相同。虚拟机栈描述的是Java方法执行的线程内存模型:每个方法被执行的时候,Java虚拟机都会同步创建一个栈帧 (Stack Frame)用于存储局部变量表、操作数栈、动态连接、方法出口等信息。'每一个方法被调用直至执行完毕的过程,就对应着一个栈帧在虚拟机栈中从入栈到出栈的过程。' 局部变量表: 存放了编译期可知的各种Java虚拟机基本数据类型(boolean、byte、char、short、int、float、long、double)、对象引用(reference类型, 它并不等同于对象本身,可能是一个指向对象起始地址的引用指针,也可能是指向一个代表对象的句柄或者其他与此对象相关的位置)和returnAddress类型(指向了一条字节码指令的地址)。 '异常抛出' 如果线程请求的栈深度大于虚拟机所允许的深度,将抛出StackOverflowError异常;如果Java虚拟机栈容量可以动态扩展,'其实HotSpot虚拟机的栈容量是不可以动态扩展的' 当栈扩展时无法申请到足够的内存会抛出OutOfMemoryError异常。 本地方法栈('执行的是虚拟机用到的native方法服务') 一个native方法就是一个Java调用非Java代码的接口。 本地方法栈也会在栈深度溢出或者栈扩展失败时分别抛出StackOverflowError和OutOfMemoryError异常。
(共享)(-Xmx -Xms)("是垃圾收集器管理的主要区域。")
  "堆内存用于存放由new创建的对象和数组,集合等,String new 出来和不是new出来的存储地方不同。"保存所有引用数据的真实信息;存放对象实例,几乎所有的对象都是存在堆中的,被'所有的线程共享。' 包括原始类型的封装类(如Byte、Integer、Long等等),'不管对象是属于一个成员变量还是方法中的本地变量,它都会被存储在堆区。' 新生代和老年代 其中新生带存放新生的对象或者年龄不大的对象,老年代则存放老年对象。   新生代分为eden区、s0区、s1区,"s0和s1也被称为from和to区域,他们是两块大小相等并且可以互相角色的空间。"   绝大多数情况下,对象首先分配在eden区,在新生代回收后,如果对象还存活,则进入s0或s1区,之后每经过一次   新生代回收,如果对象存活则它的年龄就加1,对象达到一定的年龄后,则进入老年代。
堆栈的存储
  1、基础数据类型直接在栈空间分配;
  2、方法的形式参数,直接在栈空间分配,当方法调用完成后从栈空间回收;
  3、引用数据类型,需要用 new 来创建,既在栈空间分配一个地址空间,又在堆空间分配对象的类变量;
  4、方法的引用参数,在栈空间分配一个地址空间,并指向堆空间的对象区,当方法调用完后从栈空间回收;
  5、局部变量 new 出来时,在栈空间和堆空间中分配空间,当局部变量生命周期结束后,栈空间立刻被回收,堆空间区域等待 GC 回收;
  6、方法调用时传入的实际参数,先在栈空间分配,在方法调用完成后从栈空间释放;
  7、字符串常量在 DATA 区域分配 , this 在堆空间分配;
  8、数组在栈空间分配数组名称, 在堆空间分配数组实际的大小!
  9.'Static类型的变量以及类本身相关信息都会随着类本身存储在堆区。
  10.一个本地变量也有可能是一个对象的引用,这种情况下,这个本地引用会被存储到栈中,但是对象本身仍然存储在堆区。
  11.对于一个对象的成员变量,不管它是原始类型还是包装类型,都会被存储到堆区。
  12.堆中的对象可以被多线程共享。如果一个线程获得一个对象的应用,它便可访问这个对象的成员变量。如果两个线程同时调用了同一个对象的同一个方法, 那么这两个线程便可同时访问这个对象的成员变量,但是对于本地变量,每个线程都会拷贝一份到自己的线程栈中。
方法区(共享)(描述为堆的一个逻辑部分)"所以定义的方法的信息都保存方法区中"
  用于存储已被虚拟机'加载的类信息、常量、静态变量、即时编译器编译后的代码缓存等数据。' 也就是所有编译器能够被确定,能够被快速查找的内容都存放在这里,它像数组一样通过索引访问,就是专门用来做查找的" '元空间(替代永久代)' 元空间并不在虚拟机中,而是"使用本地内存"。 -XX:MetaspaceSize,初始空间大小,达到该值就会触发垃圾收集进行类型卸载,同时GC会对该值进行调整:如果释放了大量的空间,就适当降低该值; 如果释放了很少的空间,那么在不超过MaxMetaspaceSize时,适当提高该值。 -XX:MaxMetaspaceSize,最大空间,默认是没有限制的。 存储的是每个class的信息: 1.类加载器引用(classLoader) 2.运行时常量池(是方法区的一部分,用于存储编译器生成的各种字面量和符号引用。) 所有常量、字段引用、方法引用、属性 3.字段数据 每个方法的名字、类型(如类的全路径名、类型或接口) 、修饰符(如public、abstract、final)、属性 4.方法数据 每个方法的名字、返回类型、参数类型(按顺序)、修饰符、属性 5.方法代码 每个方法的字节码、操作数栈大小、局部变量大小、局部变量表、异常表和每个异常处理的开始位置、结 束位置、代码处理在程序计数器中的偏移地址、被捕获的异常类的常量池索引 为什么要使用元空间能代替永久代 1、字符串存在永久代中,容易出现性能问题和内存溢出。   2、类及方法的信息等比较难确定其大小,因此对于永久代的大小指定比较困难,太小容易出现永久代溢出,太大则容易导致老年代溢出。   3、永久代会为 GC 带来不必要的复杂度,并且回收效率偏低。 方法区无法满足新的内存分配需求时,将抛出OutOfMemoryError异常。
运行时常量池
  有类的版本、字段、方法、接口等描述信息外,还有一项信息是常量池表(Constant Pool Table),用于存放编译期生成的各种字面量与符号引用,'总之就是装载class文件。' 这部分内容将在类加载后存放到方法区的运行时常量池中。
常量池
  常量池主要可以分为以下几种: (1)静态常量池:即*.class文件中的常量池,class文件中的常量池不仅仅包含字符串/数字这些字面量,还包含类、方法的信息,占用class文件绝大部分空间。 这种常量池主要用于存放两大类常量:字面量和符号引用量,字面量相当于Java语言层面常量的概念,如文本字符串,声明为final的常量值等;符号引用则属于编译原理    方面的概念,包括了如下三种类型的常量:类和接口的全限定名、字段名称描述符、方法名称描述符。         类的加载过程中的链接部分的解析步骤就是把符号引用替换为直接引用,即把那些描述符(名字)替换为能直接定位到字段、方法的引用或句柄(地址)。 (2)运行时常量池:虚拟机会将各个class文件中的常量池载入到运行时常量池中,即编译期间生成的字面量、符号引用,总之就是装载class文件。 字符串常量池 :字符串常量池可以理解为运行时常量池分出来的部分。加载时,对于class的静态常量池,如果字符串会被装到字符串常量池中。 整型常量池:Integer,类似字符串常量池,可以理解为运行时常量池分出来的。加载时,对于class的静态常量池装的东西,如果是整型会被装到整型常量池中。                类似的还有Character、Long等等常量池(基本数据类型没有哦)。。。
直接内存
  不在虚拟机中,称作堆外内存
 
 
 
 
 
 
 


猜你喜欢

转载自www.cnblogs.com/tianxin945/p/12359458.html