Java教程

JVM内存分配

本文主要是介绍JVM内存分配,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

JVM内存划分

首先有class文件=》由类加载器加载class文件到内存空间=》内存空间(方法去、堆、虚拟机栈、程序计数器、本地方法栈)=》垃圾回收负责对内存空间的清理工作=》

  1. class文件:Java程序编译后产生的中间代码,其将会被JVM解释执行;
  2. 类加载器:负责将class文件加载到内存,供虚拟机执行;

JVM有两种类加载器:启动类加载器(JVM实现的一部分)和用户自定义的类加载器(Java程序的一部分:ClassLoad的子类),有如下几种:

  • BootStrap ClassLoad:JVM的跟ClassLoad,由C++语言实现;JVM启动时初始化它,并由他加载JAVA_HOME下的jre/lib/rt.jar包的所有class文件,改包具有Java规范定义的所有接口;
  • Extension ClassLoad:JVM用它来加载其他扩展功能的jar包;
  • System ClassLoad:JVM用它来加载启动参数中指定的ClassPath中的jar包及目录,Sun JDK中它对应的类名为:AppClassLoader;
  • User-Defined ClassLoad:用户自定义类加载器。
  1. 方法区:方法区用来存储被虚拟机加载的类信息、常量、静态变量、编译后的代码数据,该区域所有线程共享,线程安全的;方法区可以视为一个JVM规范,在HotSport中是由Perm区来实现的方法区;

在jdk1.6及以下版本中,方法区还存放了常量池(例如字符串常量池),在jdk1.7及以后的版本中常量池被移到堆区了

  1. :虚拟机启动时创建的所有线程共享的区域,主要用来存放对象的实例(通过new操作创建的对象实例),垃圾回收的重点管理区域;
  2. 虚拟机栈:栈是线程私有的区域,栈与线程具有相同的生命周期

栈主要用来实现Java方法的调用和执行,每个方法在执行时都会创建一个栈帧用来存储方法的局部变量、操作栈、动态链接和方法出口等信息;方法调用时通过入栈和出栈操作进行栈空间的分配和释放

  1. 程序计数器:线程私有的资源,JVM会为每个线程创建单独的程序计数器

它可以被看作是当前线程执行的字节码的行号指示器,解释器的工作原理便是通过改变这个计数器来确定下一条需要被执行的字节码指令,程序的流程控制(循环、分支、异常处理)都是通过它来完成的

  1. 本地方法栈:与虚拟机栈相似,它是为虚拟机使用到的本地(Native)方法服务;

当线程调用Java方法时,JVM会创建栈帧压入虚拟机栈,但当调用的是本地方法时,虚拟机栈保持不变,而是简单的动态链接并直接调用指定的本地方法,如果本地方法接口使用的是C++连接模型,那么它的本地方法栈就是C++栈

  1. 执行引擎:主要负责执行字节码,方法的字节码是由Java虚拟机的指令序列构成,每条指令包含一个单字节的操作码,后面跟随0个或多个操作数;执行引擎执行时先取操作码后取操作数,以此类推执行

运行时内存划分

年轻代、老年代和永久代

根据对象的生命周期长短不同将其分为不同的类型(年轻代、老年代和永久代)并分别进行垃圾回收:分代垃圾回收

主要思路:把堆分为两个或多个子堆,每个子堆被视为一代,在运行中优先收集年幼对象,如果一个对象经过多次收集仍然存活那么就把它转移到高一级的堆里,以减少对其扫描的次数;目前最常用的JVM是SUN(现被Oracle收购)公司的HotSport所采用的算法是分代回收

  • 年轻代Young Generation:分为3个部分(一个Eden区和两个相同的Survivor区=from和to区)Eden区主要用来存储新建的对象,在使用复制回收算法时作为双缓存起到内存整理的作用,Survivor区始终有一个是空的
  • 老年代Old Generation:主要存储生命周超长的对象、超大(无法在年轻代分配)的对象;
  • 永久代Permanent Generation:存放代码、字符串常量池、静态变量等可以持久化的数据;是一片连续的堆空间;基本不参与垃圾回收;SunJDK把方法区实现在了永久代。

新建的对象优先在Eden区分配内存,如果Eden已满则在创建对象时会因无法申请到空间而触发minorGC,minorGC主要用来对年轻代进行回收:把Eden区不能回收的对象放到空的Survivor区,另一个Survivor区里不能回收的也放到这里,这样就保证有一个Survivor区是空的;但是如果Survivor区也满了或者有些对象已经存在了很长时间,那么这些对象会被放到老年代中,老年代满触发fullGC。
fullGC是用来清理整个堆空间的,会造成很大的系统资源开销,应尽量避免,触发fullGC的情况:

  • 调用System.gc()方法
  • 老年代空间不足;根据实际情况增大Survivor区、老年代空间或调低触发并发GC(并发垃圾回收)的比率
  • 永久代满;可以增大永久代空间(设置永久代大小:-XX:MaxPermSize=16m)、开启CMS回收永久代选项、应用程序线程并发的垃圾回收线程进行垃圾回收操作
    Java8已移除了永久代,新加了一个称为元数据区的native内存区,大部分类的元数据都在本地内存中分配。
String.intern()
String s1 = new String("abc"); // 等价于"abc"和new String()两个操作,先从常量池取"abc"再作为参数传入构造方法
String s2 = "abc";

只要使用new总会生成新的对象;String的实现采用了Flyweight的设计模式优先使用常量池中存在的对象,s2时"abc"在编译时产生并放到常量池中,s1时在运行时把"abc"放到堆中;

  • 直接使用双引号声明的String对象会直接存储在常量池中;
  • 通过String的intern方法把字符串放到常量池中;该方法在jdk1.6和1.7中的工作原理不同;有空细聊。。。
元空间MetaSpace

jdk1.8开始永久代被移除,类的元数据被放到了本地化的堆内存(native heap)中即MetaSpace元空间;
移除永久代的原因:

  • 永久代内存经常不够用或发生内存泄漏而抛出异常:Java Web开发需要动态生成类,而元空间有非常大的存储空间;
  • 可以促进HotSpot JVM和JRockit VM的融合,JRockit没有永久代;
  • 在HotSpot中每个垃圾回收器都需要专门的代码来处理存储在永久代中类的元数据信息,把类的信息转移到MetaSpace(与Java Heap有相同的地址空间)后可以实现MetaSpace与Java Heap的无缝化管理,简化FullGC的过程

MetaSpace的内存分配
MetaSpaceVM分配内存:类与类加载器有相同的生命周期,只有类加载器还存活,MetaSpace中的类信息就不能释放,MetaSpaceVM通过一个块分配器来管理内存的分配,块的大小取决于类加载器的类型。VM维护一个全局的块列表,类加载器按需取块,当类加载器生命周期结束时释放块,把申请的块归还到全局块列表,每个块被分为多个block,每个block存储一个元数据单元;
由于类的大小不固定,当类加载器需要一个块时,有可能空闲的块太小而不足以容纳当前类,就会出现内存碎片,目前MetaSpaceVM还没有解决这个碎片问题。

MetaSpace还增加了一些参数可配置

这篇关于JVM内存分配的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!