# jvm了解一下~

参考资料《深入理解java虚拟机》

java内存区域

运行时数据区域

image
  1. 程序计数器 :可以看成是当前线程所执行字节码的行号指示器。
    • 每个线程都需要一个独立的程序计数器,所以是私有的。(java虚拟机多线程是通过线程轮流切换并分配处理器执行时间的方式)
    • 如果线程执行的是java方法,计数器记录的是字节码指令的地址;如果是native方法,计数器为空(uundifined),是唯一没有outOfMemoryError的区域。
      • native是本地方法,和平台有关,需要借助c语言。

  2. 虚拟机栈 :线程私有的,生命周期和线程相同。
    • 描述的是java方法执行的内存模型。每个方法在执行时都会创建一个栈帧,用来记录局部变量、动态链接,方法出口等。每个方法的执行 从开始到介绍就是一个栈帧从虚拟机栈入栈到出栈的过程。
      • 什么是栈帧呢?栈帧可以理解为一个方法的运行空间。它主要由两部分构成,一部分是局部变量表,方法中定义的局部变量以及方法的参数就存放在这张表中;另一部分是操作数栈,用来存放操作数。

    • 这个区域规定了两种异常
      • 如果线程请求的栈深度大于虚拟机允许的栈深度,则抛出 stackOverFlowError,比如递归调用
      • 如果虚拟机栈可以动态扩展,但是扩展时申请不到足够的内存,则抛出OutOfMemoryError,比如这个线程运行时创建大量的类。
  3. 本地方法栈 和虚拟机栈类似。区别是虚拟机栈为执行java方法服务,本地方法栈为运行native服务。
  4. java堆 重点来了~ 下面重点分析
    • 是java虚拟机中内存区域最大的一块
    • 是被所有线程共享的区域,虚拟机启动时创建。
    • 几乎所有的对象实例都会在这分配空间
    • 可以是物理上不连续的区域,只要是逻辑上连续即可
    • 如果堆中没有内存可以分配,并且不能扩展的话,抛出 OutOfMemoryError异常
  5. 方法区 non-heap (非堆)
    • 是各个线程的共享区域
    • 用于存放虚拟机加载的类信息,常量,静态变量、即时编译器编译的代码等。
    • 并不能完全等同于永久代(permanent generation)
    • 垃圾回收在这比较少出现,回收目标是常量池和对类型的卸载
  6. 运行时常量池,是方法区的一部分。
    • class文件除了记录类的版本,字段、方法等,还有常量池,用于存放编译期生成的字面量和符号引用,在类的加载后进入该区域。
    • 具有动态性,运行期间也可以将新的常量放入池中。比如string类中的 intern()方法

      string.intern() : 如果字符串常量池中已经包含一个等于string对象的字符串,则返回池中这个字符串的对象,否则,将此字符串对象包含的字符串放入常量池中,并返回次string对象的引用。

    • 受到方法区的限制,当不能申请内存时,抛出OutOfMemoryError异常
  7. 直接内存:不属于虚拟机内存,但是有可能导致OutOfMemoryError异常

虚拟机对象

……

OutOfMemoryError异常分析

堆溢出

 * VM Args: -Xms(堆的最小值)20m -Xmx(堆的最大值)20m:都设置成20m  防止堆内存自动扩展
 * - XX:+HeapDumpOnOutOfMemoryError  oom时生成dump文件
 
public class HeapOOM {
    static class OOMObject {
    }

    public static void main(String[] args) {
        List<OOMObject> list = new ArrayList<HeapOOM.OOMObject>();
        while (true) {
            list.add(new OOMObject());
        }
    }
}
运行结果:
Exception in thread "main" Heap dump file created [2314620069 bytes in 32.813 secs]
java.lang.OutOfMemoryError: Java heap space
  • 内存泄露
    • 查看泄露对象到gc root的引用链(不太会找 哈哈)
  • 内存溢出
    • 检查堆参数 xms xmx,是否还能调大。检查代码是否存在对象生命周期过长等情况。

虚拟机栈和本地方法栈溢出

单线程

==Xss:设置每个线程的堆栈大小==

/**
 * hotspot虚拟机不区分虚拟机栈和本地方法栈 所以只设置xss
 * VM Args:-Xss128k
 */

public class JavaVMStackSOF {

    private int stackLength = 1;

   
  
    public void stackLeak() {
        stackLength++;
        stackLeak();
    }

    public static void main(String[] args) {
        JavaVMStackSOF oom = new JavaVMStackSOF();
        try {
            oom.stackLeak();
        } catch (Throwable e) {
            System.out.println("stack length: " + oom.stackLength);
            throw e;
        }
    }
}

运行结果:
stack length: 978
Exception in thread "main" java.lang.StackOverflowError
at JavaVMStackSOF.stackLeak(JavaVMStackSOF.java:10)
……
  • 如果线程请求的栈深度大于虚拟机允许的栈深度,则抛出 stackOverFlowError
  • 虚拟机栈扩展栈时申请不到足够的内存,则抛出OutOfMemoryError

当栈空间无法分配时,是已使用的栈空间太大,还是内存空间太小?不管是调用xss减少栈内存容量,还是增大本方法中本地变量表的长度,当内存无法分配时,都抛出stackOverFlowError异常。

多线程
  • 多线程下的内存溢出,与栈空间是否大不存在任何联系
  • 同等物理内存下,为栈每个栈空间分配的内存越大,可以创建的线程就越少。
  • 如果不能减少线程数,就只能减少最大堆(增加虚拟机栈的内存?)和减少栈容量来获得更多线程。
public class JavaVMStackOOM {

    private void dontStop() {
        while(true) {
        }
    }

    // 多线程方式造成栈内存溢出 OutOfMemoryError
    public void stackLeakByThread() {
        while(true) {
            Thread thread = new Thread(new Runnable() {
                @Override
                public void run() {
                    dontStop();
                }
            });
            thread.start();
        }
    }

    public static void main(String[] args) {
        JavaVMStackOOM oom = new JavaVMStackOOM();
        oom.stackLeakByThread();
    }
}
运行结果:
Exception in thread "main" java.lang.OutOfMemoryError: unable to create new native thread

方法区和运行时常量池溢出

方法区用于存放class的信息,如类名、修饰符、常量池、字段描述等。对于该区域的测试,==思路就是运行时产生大量的类去填满方法区,直到溢出。==

  • CGLib动态生成类导致的方法区溢出
/** 
 * -XX:PermSize=10M -XX:MaxPermSize=10M
 */
public class JavaMethodAreaOOM {
    public static void main(String[] args) {
        while(true) {
            Enhancer enhancer = new Enhancer();
            enhancer.setSuperclass(OOMObject.class);
            enhancer.setUseCache(false);
            enhancer.setCallback(new MethodInterceptor() {
                @Override
                public Object intercept(Object obj, Method m, Object[] objs, MethodProxy proxy) throws Throwable {
                    // TODO Auto-generated method stub
                    return proxy.invokeSuper(obj, objs);
                }
            });
            enhancer.create();
        }
    }
}
运行结果:
Caused by: java.lang.OutOfMemoryError: PermGen space
    at java.lang.ClassLoader.defineClass1(Native Method)
    ......

对程序的讲解参考 CGLIB enhancer讲解

  • 在经常动态生成大量class的应用中,要特别注意类的回收情况,

  • CGLib:Code Generation Library

    • CGLIB是一个强大的、高性能的代码生成库。其被广泛应用于AOP框架(Spring、dynaop)中,用以提供方法拦截操作
    • 原理:动态生成一个要代理类的子类,之类要实现代理类的所有方法(除了final修饰的)。在之类中利用方法拦截技术拦截所有代理类的方法的调用,顺势织入横切逻辑。
  • CGLIB和Java动态代理的区别

    • Java动态代理只能够对接口进行代理,不能对普通的类进行代理(因为所有生成的代理类的父类为Proxy,Java类继承机制不允许多重继承);CGLIB能够代理普通类;
    • Java动态代理使用Java原生的反射API进行操作,在生成类上比较高效;CGLIB使用ASM框架直接对字节码进行操作,在类的执行过程中比较高效

本地内存直接溢出(感觉不常用,略,有兴趣可以参考《深入理解java虚拟机 2.4.4小节》)

垃圾收集器和内存分配

对象还存活吗?
  • 判断对象是否存活,并不是给对象添加一个引用计数器。尽管有时候效率还是很高,java虚拟机并没有采用,因为没办法解决对象之间相互循环引用的问题。
  • java中是采用可达性分析算法来判断对象是否存活的。
    • 算法思想:通过GC root的对象作为起点,从这个节点向下搜索,搜索经过的路径叫做引用链,当一个对象到GC root没有任何引用链项链,就认为对象是不可用的。


      image
    • java中GC root对象包括以下几种
      • 虚拟机栈中引用的对象
      • 方法区中类静态属性引用的对象
      • 方法区中常量引用的对象
      • 本地方法栈中native引用的对象
引用
  • 强引用
    • 程序代码中普遍存在的,例如Object obj=new Object(),只要强引用存在,永远不会被回收
  • 软引用
    • 有用但并非必需的 。在内存溢出之前,会对这些对象列进回收范围进行二次回收。如果回收后还没有内存,就会抛出内存溢出异常。
  • 弱引用
    • 当垃圾收集工作时,不管内存是否足够,都会被回收。
  • 虚引用
    • 为一个对象设置虚引用的唯一目的,就是在回收时会得到一个通知。
生存还是死亡?
  • 如果对象在进行可达性分析之后发现没有GC root相连,那他将会进行一次标记,并进行一次筛选,筛选的条件是有没有必要执行finalize()方法。
    • 当对象没有覆盖finalize()方法,或者虚拟机已经执行过finalize(),则判定为不执行。
    • 如果判定为执行,会将对象放入一个F-Queue中,稍后去执行。执行时会进行二次标记,这个时候如果与引用链建立关联,就可以拯救自己了~~~~~~
回收方法区
  • 回收效率低,而且回收条件非常苛刻。
  • 主要回收废弃常量和无用的类。

垃圾收集算法

标记-清除(Mark-Sweep)

  • 首先标记出需要回收的对象,标记完成后统一回收
  • 缺点:
    • 效率不高:标记和清除效率都不高
    • 空间问题:会导致大量的内存碎片 程序需要分配较大对象时,无法找到连续的内存,不得不提前再进行内存回收。


      标记-清除

复制

  • 将内存氛围两份,每次只使用一份,当这一块用完了,就将还存活的对象复制到另一块上,然后再把这一块内存清空。
  • 好处
    • 不会产生内存碎片,只需移动堆顶指针,顺序分配,操作简单,运行高效。
  • 缺点
    • 将内存缩小一半,代价太大。


      image
  • 应用中 并不是按照1:1的比例划分内存。而是把eden和survivor按照8:1分配。回收时,将eden和from sur中存活的对象一次性的放到to sur中。当survivor内存不够时,需要old gen进行分配担保。这些对象将直接进入老年代区域。(==详细的参考下面的内存分配与回收==)

小问题,为啥要有两个survivor?

当你把eden和from sur复制到to sur中后,清除eden和from 。现在只有to中有数据了。
to中有数据 怎么做下一次的minor gc呢? 所以 要把to中的数据 再复制到from中!!!!

标记-整理

  • 复制算法在对象存活率较高的情况的下,需要进行大量的复制,效率不高。而且还需要额外的空间进行担保,所以老年代不用这种算法。
  • 算法和 标记-清除差不多,只不过是在标记之后不是对回收对象进行清除,而是让存活对象向一端移动,然后直接清理掉端边界以外的内存。


    image

分代收集

  • 根据对象存活周期的不同讲内存分为几块。
  • 一般java堆分为新生代和老生代
    • 新生代大批对象死去,少量存活,就采用 复制算法。
    • 老生代存活率高 就用标记-清除 或者标记-整理。

内存分配与回收

image
  1. 对象优先再Eden区分配
    • 当eden没有足够的内存分配时,虚拟机讲发起一次Minor GC
  2. 大对象直接进入老年代
    • 最典型的大对象就是那种很长的字符串或数组。
    • 虚拟机提供参数 -Xx:PretenureSizeThreshold 大于这个设置值的直接进入老年代
    • 目的:是为了避免eden和survivor之间发生大量的内存复制
  3. 长期存活的对象直接进入老年代
    • 虚拟机给每个对象定义了一个对象年龄计算器
    • 如果这个对象在eden出生,并经过一次minor gc扔存活,并且能够被survivor容纳,年龄+1
    • 对象在survivor中熬过一次minor gc,年龄+1
    • 当到达默认值(15),就晋升老年代。可以通过-Xx:MaxTenuringThreshold设置。
  4. 动态对象年龄判定
    • 如果在survivor中相同年龄所有对象大小的总和大于survivor内存的一半,年龄大于或等于该年龄的对象直接晋升老年代,不用非要限制上面那个参数设定的年龄。
  5. 空间分配担保
    • 只要老年代的连续内存空间>新生代对象总大小 或者 历次晋升的平均大小就会进行minor gc,否则进行full gc。
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 210,914评论 6 490
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 89,935评论 2 383
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 156,531评论 0 345
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,309评论 1 282
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,381评论 5 384
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,730评论 1 289
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,882评论 3 404
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,643评论 0 266
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,095评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,448评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,566评论 1 339
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,253评论 4 328
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,829评论 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,715评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,945评论 1 264
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,248评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,440评论 2 348

推荐阅读更多精彩内容

  • 内存溢出和内存泄漏的区别 内存溢出:out of memory,是指程序在申请内存时,没有足够的内存空间供其使用,...
    Aimerwhy阅读 732评论 0 1
  • 原文阅读 前言 这段时间懈怠了,罪过! 最近看到有同事也开始用上了微信公众号写博客了,挺好的~给他们点赞,这博客我...
    码农戏码阅读 5,952评论 2 31
  • 从三月份找实习到现在,面了一些公司,挂了不少,但最终还是拿到小米、百度、阿里、京东、新浪、CVTE、乐视家的研发岗...
    时芥蓝阅读 42,206评论 11 349
  • 上一篇所用的代码,是廖雪峰大大 python 数据库章节中的 SQLAlchemy 中的部分。因为踩到不少坑,所以...
    旦暮何枯阅读 152评论 0 0
  • 当功率计成为自行车爱好者们都可以负担的配件后,这种装备越来越受到人们的欢迎。那功率计是否可以应用于跑步呢?Stry...
    晃悠的老刘忙阅读 1,339评论 0 1