自动内存管理机制

一: 内存区域

运行时数据区内存模型
1:程序计数器
内存空间较小,可以看做是当前线程所执行的字节码行号指示器,其多线程是通过线程轮流切换并分配处理器执行时间,一个特定的时刻,为了保证切换后能回到指定的执行位置,他们的程序计数器都是独立的,即一个处理器只执行一条程序的命令。
2:Java虚拟机栈
同1一样,归属线程私有,同时生命周期与线程相同,Java方法执行的内存模型,堆栈中的栈指的就是虚拟机栈,或者说是虚拟机栈中的局部变量表(基本数据类型和对象引用)。大部分的虚拟机栈可以动态扩展。
3:本地方法栈
主要是为Native方法服务,由于虚拟机规范服本地方法栈语言,方式,数据结构没有强制规定,因此现有的虚拟机合二为一。
4:堆
Java虚拟机管理的内存中最大的一块,归属所有线程共享,用来存放对象实例,JIT编译器的发展使得“所有的对象都分配在堆上变得不是那么绝对了”,GC的主要区域,收集器在这里大多数采用分代回收,其内存空间只需要保证逻辑上是连续方可,通过Xmx Xms控制其扩展,没有内存扩展是OOM.
5:方法区
同4一样,归各个线程共享,用来存储虚拟机加载的类的信息,常量,静态变量,即时编译器编译的代码等等。虚拟机规范中它属于堆,然而别名叫Non-Heap.同时他也归属GC的范围,其本质与老年代并不等价,比堆内存更宽松的规范在于可以选择性实现垃圾收集。也会抛异常。
6: 运行时常量池
归属于方法区的一部分,class文件中除了有类的版本,filed,function,接口等信息,另外一项就是它,存放的是编译期生成的字面量与符号引用,虚拟机对常量池的规范远远松于Class文件。
7:直接内存
其不属于虚拟机运行时数据区一部分,更不是Java虚拟机中定义的内存区域,但是被频繁调用,从jdk1.4开始加入了NIO类,引入了Channel & Buffer的I/O方式,通过native函数库直接分配堆外内存,引用Java堆中的DirectByteBuffer对象,避免Java堆与native堆之间数据来回复制,提高性能。给虚拟机配置参数时候留心他的存在,避免OOM。

二:内存溢出异常

堆内存溢出:
不断创建对象,而且保证GC Roots到对象之间有可达路径避免被回收,达到最大的容量后就直接抛异常。分析异常时候两个起点,内存泄漏还是内存溢出,通过镜像分析工具进行快照分析,重点是内存里的对象是否是必要的,泄漏的话就看泄漏对象与GC Roots的引用链,给泄漏的代码进行定位。
Otherwise,就是查Xmx与-Xms了,是否可以调大,或者是加内存。或者是代码里哪块对象生命周期过长,占有内存时间过长,是否可以降低运行时的内存消耗。

@Test
    public  void heap() {

        List list = Lists.newArrayList();
        while (true){
            list.add(new CustomerTest());
        }
    }

栈内存溢出:
HotSpot虚拟机本身有-Xoss参数,实际没卵用,还是得设置-Xss参数。

private int stackLength=1;
    @Test
    public void stackLeak(){
        stackLength++;
        stackLeak();
    }

方法区和运行时常量池溢出:
String的intern()是native方法,作用:当调用 intern 方法时,如果常量池中已经有该字符串,则返回池中的字符串;否则将此字符串添加到常量池中,并返回字符串的引用,Java6以及之前的版本首次出现的字符串复制在永久代,Java7在常量池中记录的同样是首次出现的实例引用。通过参数限制方法区大小后,间接地限制了常量池容量,就会抛出PermenGen space异常。

针对于不同的Java6 7 8版本结果不同,有兴趣可以细聊哦!

@Test
    public void internn(){
        String str1 = new StringBuilder("我爱").append("工作").toString();
        System.out.println(str1.intern() == str1);

        String str2 = new StringBuilder("工作").append("使我快乐").toString();
        System.out.println(str2.intern() == str2);
    }

本机直接内存溢出:
直接内存如果没有指定,则与Java堆最大值一样。发生此异常明显的特征是Heap Dump的文件没有明显异常,但是文件很小,考虑程序是否直接或者间接引用NIO。DirectByteBuffer分配内存抛异常是通过计算得知无法分配,手动抛出来的。

 @Test
    public void DirectMemoryOOM() throws Exception {
        Field declaredField = Unsafe.class.getDeclaredFields()[0];
        declaredField.setAccessible(true);
        Unsafe unsafe = (Unsafe) declaredField.get(null);
        while (true){
            unsafe.allocateMemory(_1MB);
        }
    }

三:对象已经“死”了吗

1:引用计数法:
通过引用计数器进行计数操作,判断与0的关系。
2:可达性分析算法:
通过“GC Roots”对象为起点,判断接下里的对象有无直接引用链与起点对象直接引用,没有可达的就判定为可回收。起始对象一般为虚拟机栈引用的对象,方法区中静态属性以及常量引用的对象,本地方法栈中JNI引用的对象
3:对象引用:
上述两例都是在描述引用,JDK1.2之后引入了强引用,软引用(内存溢出之前,列入第二次回收范围,内存还不够,则OOM),弱引用(只能生存到下一次垃圾回收之前,无论是否引用都被回收),虚引用(不会对对象生存时间产生影响,他也不能作为获取对象实例的依据,目的就是对象被回收时收到系统通知)。
4:生存还是死亡:
依据就是此对象是否有必要执行finalize()方法,当对象没有覆盖此方法或者此方法被虚拟机调用过,都视为“没有必要执行”。
有必要执行的时候,放到F-Queue队列中,然后虚拟机自行创建低优先级的Finalizer线程去执行它。这里的“执行”,并不是一个执行完结束后再执行下一个,如果执行缓慢或者死循环会导致队列里其他对象等待,严重的话内存回收系统崩溃。会执行两次标记,如果第二次还不能与引用链上的任何一个对象关联,就要被回收。
5:回收方法区:
收集效率贼低,主要回收废弃常量和无用的类。废弃常量就是常量池的常量没有被引用。无用的类:1 所有的实例都被回收,也就是Java堆中不存在该类的任何实例。2 加载该类的class loader已被回收。3 该类的java.lang.Class对象没有在任何地方被引用,无法通过反射获取该对象。

四:垃圾收集算法

1: 标记-清除算法:
最基础的方法,分为标记和清除两个阶段,先标记要回收的对象,然后统一回收。两个缺陷一是效率问题两个阶段效率都不高,二来呢空间问题,会产生大量的不连续的内存碎片,引发当较大对象进入时候,由于空间碎片太多,会提前触发回收。
2:复制算法:
为解决1效率而生,将内存等分为两块,每次回收只针对原来空间的一半,当他这边内存不够时候,将存活的对象移到另外一块内存区域,然后将标记的清理掉。这个算法主要用于回收新生代,也就是Eden区和两个较小的Survivor区域(8:1),回收时候会将Eden区域和S1区域存活的对象复制到S2区域,然后清理掉刚才用过的空间。当然,如果Survivor区域生存空间不够,会依赖老年代内存进行分配担保。
3:标记-整理算法:
如2所述,当对象存活率较高时候,就会进行更多的复制操作,从而影响效率,而且“浪费”一半空间,要么就得找额外的空间进行分配担保,以应对内存中对象100%存活的极端情况。所以针对老年代的对象提出了这种算法,见名知意,标记过程与1一样,整理这个阶段不再是直接对对象直接清理,而是让存活的对象向一个方向移动,然后清理掉端边界以外的内存。
4:分代收集算法:
当前大多数的商业虚拟机都是采用“分代收集”,根据对象存活周期的不同将对象分为划分为几块,一般的Java堆分为新生代和老年代,新生代一般采用复制算法,而老年代一般采用“标记-整理”或“标记清除”进行回收。

五:垃圾收集器

1:Serial收集器
2:ParNew收集器
3:Parallel Scavenge收集器
4:Serial Old收集器
5:Parallel Old收集器
6:CMS收集器
7:G1收集器

六:内存分配与空间分配担保

对象优先在Eden区域分配,没有内存空间就Minor GC,大对象直接进入老年代,代表型就是很长的字符创以及数组,长期存活的对象将进入老年代。在Minor GC时候会检查老年代可用的连续空间是否大于新生代所有对象总空间,成立可保证这次回收是安全的,否则会查看HandlePromotionFailure设置值,如果允许担保失败,再次检查老年代的空间是否大于历次晋升到该区域对象的平均大小,如果大于将Minor GC(有风险),否则就会改为FullGC。

七:JVM参数解释

public static void main(String[] args) {

       /* List list = new ArrayList();
        list.add("");
        list.add("");
        list.add("");
        list.add(null);
        list.add(null);
        list.add(null);
        list.add(null);
        System.out.println("收拾收拾"+list.size());*/
        
        int a = 0;
        while (true){
            a++;
        }

    }
-Xms=200M  -Xmx200M -XX:NewSize=100M -Xmn100M -XX:SurvivorRatio=8

-XX:OldSize=60M -XX:PermSize=50M -XX:MaxPermSize=50M

运行上述代码,通过jps命令获取到进程pid,然后通过jmap -heap pid就可以查看内存分配和使用情况。

>jmap -heap 8912

Attaching to process ID 8912, please wait...

Debugger attached successfully.

Client compiler detected.

JVM version is 24.60-b09

 

using thread-local object allocation.

Mark Sweep Compact GC

 

Heap Configuration:

   MinHeapFreeRatio = 40

   MaxHeapFreeRatio = 70

   MaxHeapSize      = 209715200 (200.0MB)

   NewSize          = 104857600 (100.0MB)

   MaxNewSize       = 104857600 (100.0MB)

   OldSize          = 62914560 (60.0MB)

   NewRatio         = 3

   SurvivorRatio    = 8

   PermSize         = 52428800 (50.0MB)

   MaxPermSize      = 52428800 (50.0MB)

-Xms-Xmx (-XX:InitialHeapSize 和 -XX:MaxHeapSize):指定JVM初始占用

的堆内存和最大堆内存。JVM也是一个软件,也必须要获取本机的物理内存,然后JVM

会负责管理向操作系统申请到的内存资源。JVM启动的时候会向操作系统申请 -Xms 设

置的内存,JVM启动后运行一段时间,如果发现内存空间不足,会再次向操作系统申请

内存。JVM能够获取到的最大堆内存是-Xmx设置的值。

-XX:NewSize 和 -Xmn(-XX:MaxNewSize)指定JVM启动时分配的新生代内存

和新生代最大内存。

-XX:SurvivorRatio:设置新生代中1个Eden区与1个Survivor区的大小比值。在

hotspot虚拟机中,新生代 = 1个Eden + 2个Survivor。如果新生代内存是10M,

SurvivorRatio=8,那么Eden区占8M,2个Survivor区各占1M。

-XX:NewRatio:指定老年代/新生代的堆内存比例。在hotspot虚拟机中,堆内存 =

新生代 + 老年代。如果-XX:NewRatio=4表示年轻代与年老代所占比值为1:4,年轻代占

整个堆内存的1/5。在设置了-XX:MaxNewSize的情况下,-XX:NewRatio的值会被略,

老年代的内存=堆内存 - 新生代内存。老年代的最大内存 = 堆内存 - 新生代 最大内存。

-XX:OldSize:设置JVM启动分配的老年代内存大小,类似于新生代内存的初始大

小-XX:NewSize。

-XX:PermSize-XX:MaxPermSize:指定JVM中的永久代(方法区)的大小。

可以看到:永久代不属于堆内存,堆内存只包含新生代和老年代。

名词解释

Minor GC:

      从年轻代空间(包括 Eden 和 Survivor 区域)回收内存被称为 Minor GC。 Major GC 是清理老年代。	

Full GC

 是清理整个堆空间—包括年轻代和老年代。

GC Roots:

 从永久代到年轻代的引用被当成 GC roots,从年轻代到永久代的引用在标记阶段被直接忽略掉。z
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值