深入理解JVM内存模型

1、程序计数器在虚拟机的概念模型里字节码解释器工作时就是通过改变

这个计数器的值来选取下一条需要执行的字节码指令,分支、循环、跳转、异常处理、

Java 虚拟机的多线程是通过线程轮流切换并分配处理器执行时间的方式来实现

的,在任何一个确定的时刻,一个处理器(对于多核处理器来说是一个内核)只会执行

一条线程中的指令。因此,为了线程切换后能恢复到正确的执行位置,每条线程都需要

有一个独立的程序计数器,各条线程之间的计数器互不影响,独立存储,我们称这类内

存区域为“线程私有”的内存。

线程恢复等基础功能都需要依赖这个计数器来完成。

2、线程栈VM栈的默认大小是1M。用于在调用函数时持有局部变量并维护状态的内存区域

-Xss 128k:设置每个线程的堆栈大小。不能无限生成,经验值在3000~5000左右

线程栈的大小是个双刃剑,如果设置过小,可能会出现栈溢出,特别是在该线程内有递归、大的循环时
时出现溢出的可能性更大,如果该值设置过大,就有影响到创建栈的数量,如果是多线程的应用,就会
出现内存溢出的错误.

异常 :Fatal: Stack size too small

异常的引起一般是线程数目太多

3、本地方法栈

即为一些Native方法分配的stack

Native方法,java调用C程序

在Java中用System.loadLibrary()方法加载第四步产生的动态链接库文件,这个native()方法就可以在Java中被访问了。

  1.为了使用底层的主机平台的某个特性,而这个特性不能通过JAVA API访问

  2.为了访问一个老的系统或者使用一个已有的库,而这个系统或这个库不是用JAVA编写的

  3.为了加快程序的性能,而将一段时间敏感的代码作为本地方法实现。

4、栈每个线程的栈都是该线程私有的,堆则是所有线程共享的,这里说的堆,主要指程序能控制的,包括

新生代:The New Generational Heap,默认4M,此区域一般为JVM内存的1/15大小。此代分为Eden space区,Survivo space区可以看成emptySurvivo区,Survivor区,

每次算法开始都得停止当前所有的线程,然后把Survivor区的所有活跃的对象复制到emptySurvivo区,然后对Survivor区空间进行清除变成emptySurvivo,以前的emptySurvivo成为了Survivor区。(互换)

当new 一个对象时,首先是在Eden space区,当Eden space区满时,Survivor space区进行垃圾回收(此处复制算法),当对象在Survivo space区经过几次回收Tenured Generation

老年代:此处储存年老代的对象,此处的垃圾回收采用The Mark and Sweep 算法

5、垃圾回收算法: 标记-清除算法从根集合进行扫描,对存活的对象对象标记,标记完毕后,再扫描整个空间中未被标记的对象,进行回收,并且仅对不存活的对象进行处理,在存活对象比较多的情况下极为高效,但由于标记-清除算法直接回收不存活的对象,因此会造成内存碎片!

复制算法:根集合扫描,并将存活对象复制到一块新的,没有使用过的空间中,这种算法当控件存活的对象比较少时,极为高效,但是带来的成本是需要一块内存交换空间用于进行对象的移动。也就是我们前面提到的
s0 s1等空间。(Survivor 0和Survivor 1(这个估计是old Generation区?))

标记整理算法:采用标记-清除算法一样的方式进行对象的标记,但在清除时不同,在回收不存活的对象占用的空间后,会将所有的存活对象往左端空闲空间移动,并更新对应的指针。标记-整理算法是在标记-清除算法的基础上,又进行了对象的移动,因此成本更高,但是却解决了内存碎片的问题。

对于新生代内存的回收(minor GC)主要采用复制算法

分代收集分代收集是利用程序有大量临时对象的特点,对象每被引用一次,代数就增加,代数小的小型对象会被回收整理,大对象只会代数增加,不会被整理。

  引用计数:当引用连接到对象时,对象计数加1,当引用离开作用域或被置为null时减1。遍历对象列表,计数为0就释放

6、垃圾回收器:因为GC主要就是对堆的回收,当然还有常量池和永久代

a、单线程程收集器serial collector:Stop The World垃圾回收器

Client下默认方式串行回收方式适合低端机器,是Client模式下的默认收集器,对CPU和内存的消耗不高,适合用户交互比较少,后台任务较多的系统。

b、并行收集器parallel collector

多线程版本的Serial收集器他是多CPU模式下的首选回收器

c、CMS又称响应时间优先(最短回收停顿)的回收器,使用并发模式回收垃圾,使用标记-清除算法,CMS对CPU是非常敏感的,它的回收线程数=(CPU+3)/4,因此当CPU是2核的实惠,回收线程将占用的CPU资源的50%,而当CPU核心数为4时仅占用25%
分四步骤:在初始标记的时候,需要中断所有用户线程,在并发标记阶段,用户线程和标记线程并发执行,而在这个过程中,随着内存引用关系的变化,可能会发生原来标记的对象被释放,进而引发新的垃圾,因此可能会产生一系列的浮动垃圾,不能被回收。

CMS在旧生代内存到达全部容量的68%就触发了CMS的回收!


d、G1收集器

商用高性能垃圾回收器,通过重新划分内存区域,整合优化CMS,

7、当然还一种程序调用system.gc()

调用System.gc()也仅仅是一个请求。JVM接受这个消息后,并不是立即做垃圾回收,而只是对几个垃圾回收算法做了加权,使垃圾回收操作容易发生,或提早发生,或回收较多而已。

8、垃圾回收的触发

new generation来说,当eden区的对象空大于Survivor0(假设为from)的free空间时,会发生minor gc,即对整个Survivor0区,Survivor1区进行一个复制算法垃圾回收,并且部分对象转到Old Generation

当new generation满了(即eden区+Survivor0区大于Old Generation的free空间时),将发生major gc,即对New Generation和Old Generation两代都进行垃圾回收。

所以得出,如果JVM的设置内存过大,发生GC的回收频率将越小,但是回收的时间越长(特别对new generation进行复制算法的复制时是需要停止当前所有线程的),所以并不是说JVM的内存设置的越大越好,得根据实际情况进行优化。

异常:异常 java.lang.OutOfMemoryError: Java heap space

一般是由于垃圾回收后,old generation里空间也不够用了。

9、堆内存的分配

-server时最大堆内存是物理内存的1/4,但小于1G

client 小一倍

参数:-Xms128m
表示JVM Heap(堆内存)最小尺寸128MB,初始分配
-Xmx512m
表示JVM Heap(堆内存)最大允许的尺寸256MB,按需分配

new Generation与Old Generation的比例,默认为1:2,即为2

-XX:NewRatio= 参数可以设置(也可以-XX:NewSize和-XX:MaxNewsize设置新域的初始值和最大值)

Eden与Survivor的比例,默认为32

-XX:SurvivorRation=参数可以设置

当对象默认经过1次New Generation 就转入Old Generation(这个不同文章上不同,待我确定)

10、方法区、永久栈Perm

其实也可与看成堆内存的一部分,看成永久代(Perm Space),但GC也会回收,但很少回收,它用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据,比如一个int x,在不同的环境中是不同的,此信息就存在方法区

XX:+CMSClassUnloadingEnabled -XX:+CMSPermGenSweepingEnabled 设置垃圾不回收

-server选项下默认MaxPermSize为64m
-client选项下默认MaxPermSize为32m

11、常量池

编译期已可知的常量,这部分内容将在类加载后进入方法区(永久代)存放

GC的作用主要是用来卸载类和回收常量池,当然有部分方法区,即使永久代(Perm Space)也会一定的回收运行期间也可将新内容放入常量池(最典型的String.intern()方法)

12、所有线程共享主内存
每个线程有自己的工作内存。。每个线程都有自己的执行空间(即工作内存),线程执行的时候用到某变量,首先要将变量从主内存拷贝的自己的工作内存空间,然后对变量进行操作:读取,修改,赋值等,这些均在工作内存完成,操作完成后再将变量写回主内存;

多个线程同时读写某个内存数据时,就会产生多线程并发问题,涉及到三个特 性:原子性,有序性,可见性。

原文地址:https://www.cnblogs.com/salansun/p/4763110.html