Java面试_JVM内存模型

JVM内存模型
对于大多数应用来说,Java堆是java虚拟机锁管理的内存中最大的一块。java堆是被所有线程共享的一块区域,在虚拟机启动时创建。

JVM内存结构
如下图所示:
Java面试_JVM内存模型_第1张图片

由上图可以清楚的看到JVM的内存部分分为三大部分分别是:堆内存,方法区,栈内存。其中栈内存可以在细分为java虚拟机和本地方法栈,堆内存可以划分为新生代和老年代,新生代中还可以再划分为Eden区,FromSurvivor区和ToSurvivor区,关于这个可以看我之前转载的一篇文章,戳我 ,其中一部分是线程共享的,包括Java堆和方法区;另一部分是线程私有的,包括虚拟机和本地线程栈,以及程序计数器这一小部分内存。

堆内存
对于大多数应用来说,Java堆是Java虚拟机所管理的内存中最大的一块。Java堆是被所有线程共享的一块内存区域,在虚拟机启动时创建。
此内存区域唯一的目的就是存放对象实例,几乎所有的对象实例都在这分配内存。
堆内存是所有线程共有的,可以分为两为两个部分;年轻代和老年代。

下图的Perm代表的是永久代。但是注意永久代并不属于堆内存中的一部分,同时java1.8之后永久代已经被移除。
Java面试_JVM内存模型_第2张图片

新生代(young)和老年代(old)的比例是1:2(该数值可以通过参数 -XX:NewRatio来制定 )
默认的,Eden:from:to = 8:1:1 (可以通过参数-XXSurvivorRatio 来设定)

年轻代中为什么要有Survivor区
如果没有Survivor,Eden区每进行一个Minor GC,存活的对象就会送到老年区,那么可想而知,老年区很快就被填满,触发Major GC (因为Major GC一般伴随着Minor GC,也可以看做触发了Full GC) 老年代的内存空间远比新生代,进行一次Full GC消耗的时间比Minor GC长的多。这一点在大型程序中是非常影响执行和响应速度的。
因此,我们可以得到结论:Survivor的存在意义就是减少被传送到老年化的对象,进而减少Full GC的发生,Survivor的预筛选保证,只有经历16次Minor GC还能在新生代中存活的对象,才会被送到老年代。

为什么要设置两个Survivor区
设置两个Survivor区最大的好处就是解决了碎片化。碎片化带来的冯先生极大的,严重影响Java程序的性能。堆空间被散布的对象占据不连续的内存,最直接的结果是,对重没有足够大的内存空间,接下去如果程序需要给一个内存需求很大的对象分配内存,那…。
那么要怎么解决呢,开发者就想了个办法,创立了两块survivor区,这里分别为S0,S1。首先当我们的对象在Eden区占满了之后,就启动了Minor GC,然后把Eden中存活的对象放在S0,然后清空Eden,然后在等Eden区慢了之后,就利用复制算法把Eden和S0中存活的对象复制到S1,然后把Eden区和S0区清空,然后把S0和S1互换角色。这个过程很重要要特别说明一下,因为复制算法保证了Eden和S0中的两部分内存在S1中占用连续的空间,这就避免了碎片化发生

方法区
方法区也称为“永久i代”,它用于存储虚拟机加载的类信息、常量、静态变量、是各个线程共享的内存区域。再JDK8之前的HotSpot JVM,存放这些“永久的”区域叫做永久代。永久代是一片连续的堆空间,在jvm启动之前通过命令设置参数-XX:MaxPermSize来设定永久代最大可分配的内存空间,默认大小事64M(64为JVM默认是85M)。
随着JDK8的到来,JVM不再有永久代。但可的元数据信息还在,只不过不再是存储在连续的堆空间上,而是移动到叫“Metaspace”的本地内存。

方法区或永生代相关设置

  • -XX:PermSize=64MB 最小尺寸,初始分配
  • -XX:MaxPermSize = 256MB 最大允许分配尺寸
  • -XX:+CMSClassUnloadingEnabled -XX:+CMSPermGenSweepingEnabled 设置垃圾不可回收

虚拟机栈(JVM Stack)
描述的是java方法执行的内存模型:每个方法被执行的时候都会创建一个“栈帧”,用于存储局部变量表(包括参数),操作栈,方法出口等信息。每个方法被调用到执行完的过程,就对应一个栈帧在虚拟机中从入栈到出栈的过程。

本地方法栈
本地方法栈与虚拟机栈所发挥的作用是非常相似的,其区别不过是虚拟机栈为虚拟机执行java方法服务,而本地方法栈则是为虚拟机使用到的Native方法服务(native暗示这些方法是有实现体的,只不过这些实现体是非java的)。

程序计数器
程序计数器是用于标示当前线程执行的字节码文件的行号指示器。多线程情况下,每个线程都具有各自独立的程序计数器,所以该区域是非线程共享的内存区域。
当执行java方法时,计数器中保存的是字节码文件的行号,当执行Native方法时,计数器的值为空。

直接内存
直接内存并不是虚拟机内存的一部分,也不是Java虚拟机规范中定义的内存区域。jdk1.4中新加入的NIO,引入了通道与缓冲区的IO方式,它可以调用Native方法直接分配堆外内存,这个堆外内存就是本机内存,不会影响到堆内存的大小。

JVM内存参数设置
Java面试_JVM内存模型_第3张图片

  • -Xms 设置堆的最小空间大小
  • -Xmx设置堆的最大空间大小
  • -Xmn设置年轻代大小
  • -Xss 设置每个线程的堆栈大小,。JDK5.0以后每个线程堆栈大小为1M,以前每个线程堆栈大小为256K。更具应用的线程所需内存大 小进行调整。在相同物理内存下,减小这个值能生成更多的线程。但是操作系统对一个进程内的线程数还是有限制的,不能无限生成,经验值在3000~5000 左右。
  • -XX:NewSize 设置新生代最小空间大小
  • -XX:MaxNewSize 设置新生代最大空间大小
  • -XX:PermSize 设置永久代最小空间大小
  • -XX:MaxPermSize设置永久代最大空间大小
  • -XX+UserParallelGC:选择垃圾收集器为并行收集器。此配置仅对年轻代有效。即上述配置下,年轻代使用并发收集,而年老代仍旧使用串行收集。
  • -XX:ParallerGCThreads = 20 配置并行收集器的线程数,即同时多少个线程一个进行垃圾回收。此数值最好配置与处理器数目相等。

你可能感兴趣的:(Java面试)