
**方法区:**存储已被虚拟机加载的类元数据信息(元空间)
**堆:**存放对象实例,几乎所有的对象实例都在这里分配内存
虚拟机栈: 虚拟机栈描述的是|ava方法执行的内存模型:每个方法被执行的时候都会同时创建一个栈帧(Stack Frame)用于存储局部变量表、操作栈、动态链接、方法出口等信息程**序计数器:**当前线程所执行的字节码的行号指示器
**本地方法栈:**本地方法栈则是为虚拟机使用到的Native方法服务。
1、类加载器
类加载器分为四种:前三种为虚拟机自带的加载器。
(1)启动类加载器(Bootstrap)C++
功能: 负责加载制AVA_HOME中jre/lib/rt.jar里所有的class,由C++实现,不是ClassLoader子类
(2)扩展类加载器(Extension)Java
功能: 负责加载java平台中扩展功能的一些jar包,包括$JAVA HOME中ire/lib/*,jar或-Diava.ext.dirs指定目录下的jar包
(3)应用程序类加载器(AppClassLoader)Java
功能:也叫系统类加载器,负责加载classpath中指定的jar包及目录中class
(4)用户自定义加载器 Java.lang.ClassLoader的子类,用户可以定制类的加载方式
工作过程:
**1、**当AppClassLoader加载一个class时,它首先不会自己去尝试加载这个类,而是把类加载请求委派给父类加载器ExtClassLoader去完成。
**2、**当ExtClassLoader加载一个class时,它首先也不会自己去尝试加载这个类,而是把类加载请求委派给BootStrapClassLoader去完成。
**3、**如果BootStrapClassLoader加载失败(例如在$AVA_HOME/jre/lib里未查找到该class),会使用ExtClassLoader来尝试加载;
**4、**若ExtClassLoader也加载失败,则会使用AppClassLoader来加载5、如果AppClassLoader也加载失败,则会报出异常ClassNotFoundException
其实这就是所谓的双亲委派模型。简单来说:如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把请求委托给父加载器去完成,依次向上。
**好处:**防止内存中出现多份同样的字节码(安全性角度)
比如加载位于 rt.jar 包中的类 java.lang.0bject,不管是哪个加载器加载这个类,最终都是委托给顶层的启动类加载器进行加载,这样就保证了使用不同的类加载器最终得到的都是同样一个 Object对象。
2、本地接口Native Interface
本地接口的作用是融合不同的编程语言为Java 所用,它的初衷是融合 C/C++程序,Java 诞生的时候是 C/C++横行的时候,要想立足,必须有调用 C/C++程序,于是就在内存中专门开辟了一块区域处理标记为native的代码,它的具体做法是 Native Method Stack中登记 native方法,在Execution Engine 执行时加载native libraies.
目前该方法使用的越来越少了,除非是与硬件有关的应用,比如通过lava程序驱动打印机或者|ava系统管理生产设备,在企业级应用中已经比较少见。因为现在的异构领域间的通信很发达,比如可以使用Socket通信,也可以使用WebService等等,不多做介绍。
代码
new Thread().start();//进入start()方法,查看代码,能看到navite关键字信息
底层源码:
private native void starto()
3、MethodArea方法区
方法区是被所有线程共享 ,所有字段和方法字节码,以及一些特殊方法如构造函数,接口代码也在此定义。**简单说,所有定义的方法的信息都保存在该区域,**此区属于共享区间。
静态变量+常量+类信息(构造方法/接口定义)+运行时常量池存在方法区中
But
实例变量存在堆内存中,和方法区无关
4、堆栈方法区的关系

**堆:**存储]ava对象和数组,垃圾回收器负责管理。
**栈:**存储局部变量和方法调用的相关信息,每个线程有独立的。
**方法区:**存储类的信息、静态变量、常量池等数据,与类加载和初始化密切相关。
Hotspot是使用指针的方式来访问对象:
Java堆中会存放访问类元数据的地址
reference存储的就是对象的地址
5、堆
5.1、新生区
新生区是对象的诞生、成长、消亡的区域,一个对象在这里产生,应用,最后被垃圾回收器收集,结束生命。新生区又分为两部分:伊甸区(Eden space)和幸存者区(Survivor pace),所有的对象都是在伊甸区被new出来的。幸存区有两个:From区(Survivor From space)和To区(Survivor To space)。当伊甸园的空间用完时,程序又需要创建对象,IM的垃圾回收器将对伊甸园区进行垃圾回收(Minor Gc),将伊甸园区中的不再被其他对象所引用的对象进行销毁。然后将伊甸园中的剩余对象移动到幸存 From区。
MinorGc垃圾回收的过程如下:
1.eden、From 复制到To,年龄+1
首先,当Eden区满的时候会触发第一次GC,把还活着的对象拷贝到Survivor From区,当Eden区再次触发GC的时候会扫描Eden区和From区域,对这两个区域进行垃圾回收,经过这次回收后还存活的对象,则直接复制到To区域(如果有对象的年龄已经达到了老年的标准,则赋值到老年代区),同时把这些对象的年龄+1
2.清空 eden、Survivor From
然后,清空Eden和From中的对象
3. To和 From 互换
最后,To和From互换,原T0成为下一次GC时的From区。部分对象会在From和To区域中复制来复制去,如此交换15次(由IVM参数MaxTenuringThreshold决定,这个参数默认是15),最终如果还是存活,就存入到老年代
4.大对象特殊情况
如果分配的新对象比较大Eden区放不下,但0ld区可以放下时,对象会被直接分配到0ld区(即没有晋升这一过程,直接到老年代了)
MinorGC的过程:复制->清空->互换

5.2、老年代
经历多次GC仍然存在的对象(默认是15次),老年代的对象比较稳定,不会频繁的GC若养老区也满了,那么这个时候将产生MajorGc(FullGC),进行养老区的内存清理。若养老区执行了Ful GC之后发现依然无法进行对象的保存,就会产生O0M异常"OutOfMemoryError"
如果出现java.lang.0utOfMemoryError: Java heap space异常,说明java虚拟机的堆内存不够。原因有二**(1)Java虚拟机的堆内存设置不够,可以通过参数-Xms、-Xmx来调整。**
(2)代码中创建了大量大对象,并且长时间不能被垃圾收集器收集(存在被引用)。
Javà 堆从 GC 的角度还可以细分为: 新生代(Eden 区、From Survivor &和 To SuvivorR)和老年
代。

5.2、永久代(jdk1.8改为元空间)
永久存储区是一个常驻内存区域,用于存放IDK自身所携带的 Class、Interface 的元数据,也就是说它存储的是运行环境必须的类信息,被装载进此区域的数据是不会被垃圾回收器回收掉的,关闭JM 才会释放此区域所占用的内存。
对于HotSpot虚拟机,很多开发者习惯将方法区称之为"永久代(Parmanent Gen)"",但严格本质上说两者不同,或者说使用永久代来实现方法区而已,永久代是方法区(相当于是一个接口interface)的一个实现。
实际而言,方法区(Method Area)和堆一样,是各个线程共享的内存区域,它用于存储虚拟机加载的:类信息+普通常量+静态常量+编译器编译后的代码等等,虽然VM规范将方法区描述为堆的一个逻辑部分,但它却还有一个别名叫做Non-Heap(非堆),目的就是要和堆分开。
如果出现java.lang.OutOfMemoryError: PermGen space,说明是lava虛拟机对永久代Perm内存设置不够。一般出现这种情况,都是程序启动需要加载大量的第三方ar包。例如:在一个Tomcat下部署了太多的应用。或者大量动态反射生成的类不断被加载,最终导致Perm区被占满。
- Jdk1.6及之前: 有永久代,常量池1.6在方法区
- Jdk1.7:有永久代,但已经逐步"去永久代",常量池1.7在堆
- Jdk1.8及之后:无永久代,常量池1.8在堆中

永久代与元空间的最大区别之处:
永久代使用的是jvm的堆内存 ,但是java8以后的元空间并不在虚拟机中而是使用本机物理内存。因此,默认情况下,元空间的大小仅受本地内存限制。
6、堆参数调优入门


|---------------------|-------------------------------|
| 参数 | 备注 |
| -Xms | 初始堆大小。只要启动,就占用的堆大小,默认是内存的1/64 |
| -Xmx | 最大堆大小。默认是内存的1/4 |
| -Xmn | 新生区堆大小 |
| -XX:+PrintGcDetails | 输出详细的GC处理日志 |
java代码查看jvm堆的默认值大小:
Runtime.getRuntime().maxMemory()// 堆的最大值,默认是内存的1/4,单位:字节
Runtime.getRuntime().totalMemory() // 堆的当前总大小,默认是内存的1/64,单位:字节
7、总结

8、垃圾回收机制
8.1、垃圾判定
8.1.1、引用计数法(Reference-counting)
引用计数算法是通过判断对象的引用数量来决定对象是否可以被回收。给对象中添加一个引用计数器,每当有一个地方引用它时,计数器值就加1;当引用失效时,计数器值就减1;任何时刻计数器为0的对象就是不可能再被使用的。
优点:
简单,高效,现在的objective-c、python等用的就是这种算法。缺点:
引用和去引用伴随着加减算法,影响性能
很难处理循环引用,相互引用的两个对象则无法释放。
因此目前主流的Java虚拟机都摒弃掉了这种算法。
8.1.2、可达性分析算法
这个算法的基本思想就是通过一系列的称为"GC Roots"的对象作为起点,从这些节点开始向下搜索,节点所走过的路径称为引用链,当一个对象到 GC Roots 没有任何引用链相连的话,则证明此对象是不可用的。

在|ava语言中,可以作为GC Roots的对象包括下面几种:
- 虚拟机栈(栈帧中的本地变量表)中的引用对象。
- 方法区中的类静态属性引用的对象。
- 方法区中的常量引用的对象。
- 本地方法栈中INI(Native方法)的引用对象
真正标记以为对象为可回收状态至少要标记两次。
第一次标记:不在 GC Roots 链中,标记为可回收对象。
第二次标记:判断当前对象是否实现了finalize( 方法,如果没有实现则直接判定这个对象可以回收,如果实现了就会先放入一个队列中。并由虚拟机建立一个低优先级的程序去执行它,随后就会进行第二次小规模标记,在这次被标记的对象就会真正被回收了!
java
//t1 可以叫 虚拟机栈中引用的对象
GCRootsDemo tl=new GcRootsDemo(;
//方法区中的类静态属性引用的对象private static GcRootsDemo2 t2 =new GcRootsDemo2();//方法区中常量引用的对象
private static finalGCRootsDemo3 t3=new GcRootsDemo3();
8.2、四种引用
平时只会用到强引用和软引用。
强引用:
类似于 Object obj= new Object(); 只要强引用还存在,垃圾收集器永远不会回收掉被引用的对象。
软引用:
SoftReference 类实现软引用。在系统要发生内存溢出异常之前,才会将这些对象列进回收范围之中进行二次回收。如果这次回收还没有足够的内存,才会抛出内存溢出异常。软引用可用来实现内存敏感的高速缓存。
弱引用:
WeakReference 类实现弱引用。对象只能生存到下一次垃圾收集之前。在垃圾收集器工作时,无论内存是否足够都会回收掉只被弱引用关联的对象。
虚引用:
PhantomReference 类实现虚引用。无法通过虚引用获取一个对象的实例,为一个对象设置虚引用关联的唯一目的就是能在这个对象被收集器回收时收到一个系统通知。
强引用:最常用,确保对象不被回收。
**软引用:**内存不足时回收,适用于缓存。
**弱引用:**垃圾回收时回收,适用于避免内存泄露。
**虚引用:**随时可能被回收,主要用于跟踪对象回收。
8.3、垃圾收回算法
在介绍JVM垃圾回收算法前,先介绍一个概念:Stop-the-World
Stop-the-worid意味着IVM由于要执行GC而停止了应用程序的执行,并且这种情形会在任何一种GC算法中发生。当stopthe-world发生时,除了GC所需的线程以外,所有线程都处于等待状态直到GC任务完成。事实上,GC优化很多时候就是指减少Stop-the-world发生的时间,从而使系统具有高吞吐、低停顿的特点。
8.3.1、复制算法(Copying)
该算法将内存平均分成两部分,然后每次只使用其中的一部分,当这部分内存满的时候,将内存中所有存活的对象复制到另一个内存中,然后将之前的内存清空,只使用这部分内存,循环下去。
优点:
实现简单
不产生内存碎片
缺点:
将内存缩小为原来的一半,浪费了一半的内存空间,代价太高;如果不想浪费一半的空间,就需要有额外的分配担保,以应对被使用的内存中所有对象都100%存活的极端情况,所以在老年代一般不能直接选用这种算法。
如果对象的存活率很高,我们可以极端一点,假设是100%存活,那么我们需要将所有对象复制一遍,并将所有引用地址重置一遍。复制这一工作所花费的时间,在对象存活率达到一定程度时,将会变的不可忽视。所以从以上描述不难看出,复制算法要想使用,最起码对象的存活率要非常低才行,而且最重要的是,我们必须要克服50%内存的浪费。
在GC开始的时候,对象只会存在于Eden区和名为"From"的Survivor区,Survivor区"To"是空的。紧接着进行GCEden区中所有存活的对象都会被复制到'To",而在"From"区中,仍存活的对象会根据他们的年龄值来决定去向。对象在Survivor区中每熬过一次Minor Gc,年龄就会增加1岁。年龄达到一定值(年龄阈值,可以通过-XX:MaxTenuringThreshold来设置)的对象会被移动到年老代中,没有达到阈值的对象会被复制到"To"区域。**经过这次GC后,Eden区和From区已经被清空,这个时候,"rom"和"To"会交换他们的角色,也就是新的"To"就是上次GC前的"From",新的"From"就是上次GC前的"To"。**不管怎样,都会保证名为To的Survivor区域是空的。Minor GC会一直重^这样的过程,直到"To"区被填满,"To"区被填满之后,会将所有对象移动到年老代中。

因为Eden区对象一般存活率较低,一般的,使用两块10%的内存作为空闲和活动区间,而另外80%的内存,则是用来给新建对象分配内存的。一旦发生GC,将10%的from活动区间与另外80%中存活的eden对象转移到10%的to空闲区间,接下来,将之前90%的内存全部释放,以此类推。
8.3.2、标记清除(Mark-sweep)
"标记:清除"(Mark sweep)算法是几种GC算法中最基础的算法,是因为后续的收集算法都是基干这种思路并对其不足进行改进而得到的。正如名字一样,算法分为2个阶段:
1.标记出需要回收的对象,使用的标记算法均为可达性分析算法。
2.回收被标记的对象。
缺点:
效率问题(两次遍历)
空间问题(标记清除后会产生大量不连续的碎片。M就不得不维持一个内存的空闲列表,这又是一种开销。而且在分配数组对象的时候,寻找连续的内存空间会不太好找。)
8.3.3、标记压缩(Mark-compact)
标记-整理法是标记-清除法的一个改进版。同样,在标记阶段,该算法也将所有对象标记为存活和死亡两种状态;不同的是,在第二个阶段,该算法并没有直接对死亡的对象进行清理,而是通过所有存活对像都向一端移动,然后直接清除边界以外的内存。
优点:
标记/整理算法不仅可以弥补标记/清除算法当中,内存区域分散的缺点,也消除了复制算法当中,内存减半的高额代价。
缺点:
如果存活的对象过多,整理阶段将会执行较多复制操作,导致算法效率降低。

8.3.4、分代收集算法(Generational-Ccollection)
内存效率:复制算法>标记清除算法>标记整理算法(此处的效率只是简单的对比时间复杂度,实际情况不一定如此)。内存整齐度:复制算法=标记整埋算法>标记清除算法。内存利用率:标记整理算法=标记清除算法>复制算法,
可以看出,效率上来说,复制算法是当之无愧的老大,但是却浪费了太多内存,而为了尽量兼顾上面所提到的三个指标标记/整理算法相对来说更平滑一些,但效率上依然不尽如人意,它比复制算法多了一个标记的阶段,又比标记/清除多了一个整理内存的过程
难道就没有一种最优算法吗?
回答:无,没有最好的算法,只有最合适的算法。==========>分代收集算法
分代回收算法实际上是把复制算法和标记整理法的结合,并不是真正一个新的算法,一般分为:老年代(OldGeneration)和新生代 (Young Generation),老年代就是很少垃圾需要进行回收的,新生代就是有很多的内存空间需要回收,所以不同代就采用不同的回收算法,以此来达到高效的回收算法。
年轻代(Young Gen)
年轻代特点是区域相对老年代较小,对象存活率低。
这种情况复制算法的回收整理,速度是最快的。复制算法的效率只和当前存活对像大小有关,因而很适用于年轻代的回收。而复制算法内存利用率不高的问题,通过hotspot中的两个survivor的设计得到缓解。
老年代(Tenure Gen)
老年代的特点是区域较大,对象存活率高。
这种情况,存在大量存活率高的对像,复制算法明显变得不合适。一般是由标记清除或者是标记清除与标记整理的混合实现。