文章
问答
冒泡
结合jvm工具看看jdk1.8的jvm
现在面试造火箭的时候很多会问jvm的相关问题,其实我们平时工作中很少去对jvm做什么的,但是了解下还是有必要的。这里我们就结合jvm工具看下jvm的相关概念。由于当前绝大多数场景都是jdk8,所以我们就以jdk8为例。
 

1.jdk1.8的jvm模型概念

我们先看下jvm的结构图,这个图是网上找的相对比较美观且靠谱的图片。
 
 

1.1程序计数器

程序计数器(Program Counter Register)是一块较小的内存空间,它可以看作是当前线程所执行的字节码的行号指示器。在虚拟机概念模型里(概念模型,各种虚拟机可能会通过一些更高效的方式实现),字节码解释器工作时就是通过改变这个计数器的值来选取下一条需要执行的字节码指令:分支、跳转、循环、异常处理、线程恢复等基础操作都会依赖这个计数器来完成。每个线程都有独立的程序计数器,用来在线程切换后能恢复到正确的执行位置,各条线程之间的计数器互不影响,独立存储。所以它是一个“线程私有”的内存区域。此内存区域是唯一一个在JVM规范中没有规定任何OutOfMemoryError情况的区域。
 

1.2.虚拟机栈 JVM Stacks

  • JVM栈是线程私有的内存区域。它描述的是java方法执行的内存模型,每个方法执行的同时都会创建一个栈帧(Stack Frame)用于存储局部变量表、操作数栈、动态链接、方法出口等信息。每个方法从调用直至完成的过程,都对应着一个栈帧从入栈到出栈的过程。每当一个方法执行完成时,该栈帧就会弹出栈帧的元素作为这个方法的返回值,并且清除这个栈帧,Java栈的栈顶的栈帧就是当前正在执行的活动栈,也就是当前正在执行的方法。就像是组成动画的一帧一帧的图片,方法的调用过程也是由栈帧切换来产生结果。
  • 局部变量表存放了编译器可知的各种基本数据类型(int、short、byte、char、double、float、long、boolean)、对象引用(reference类型,它不等同于对象本身,可能是一个指向对象起始地址的引用指针,也可能是指向一个代表对象的句柄或其他与此对象相关的位置)和returnAddress类型(指向了一跳字节码指令的地址)。
  • 在JVM规范中,对这个区域规定了两种异常情况:如果线程请求的栈深度大于虚拟机允许的深度,将抛出StackOverflowError异常;如果虚拟机栈可以动态扩展,在扩展时无法申请到足够的内存,就会抛出OutOfMemoryError异常。

1.3.本地方法Native Method Stack

本地方法栈和虚拟机栈所发挥的作用是很相似的,它们之间的区别不过是 虚拟机栈为虚拟机执行Java方法(字节码)服务,而本地方法栈则为虚拟机使用到的Native方法服务。Sun HotSpot 直接就把本地方法栈和虚拟机栈合二为一。本地方法栈也会抛出StackOverflowError和OutOfMemoryError异常。
 

1.4.堆Heap

  • Heap是OOM故障最主要的发源地,它存储着几乎所有的实例对象,堆由垃圾收集器自动回收,堆区由各子线程共享使用;通常情况下,它占用的空间是所有内存区域中最大的,但如果无节制地创建大量对象,也容易消耗完所有的空间;堆的内存空间既可以固定大小,也可运行时动态地调整。
  • ms是memorystart的简称 最小堆容量
  • mx是memory max的简称 最大堆容量
  • 对于大多数应用来说,Java堆(Heap)是JVM所管理的内存中最大的一块。它是被所有线程共享的一块内存区域,在虚拟机启动时创建。主要用来存放对象实例,所有的对象实例以及数组都要在堆上分配。堆是垃圾收集器管理的主要区域,也被称为“GC堆”,从内存回收的角度来看,堆可以细分为:新生代和老年代;再细致一点可分为:Eden空间、From Survivor空间、To Survivor空间(空间分配比例是8:1:1)。如果在堆中没有内存完成实例分配,并且堆也无法再扩展时,将抛出OutOfMemoryError异常。

1.5.Metaspace元空间

  • 在JDK1.8中,永久代已经不存在,存储的类信息、编译后的代码数据等已经移动到了MetaSpace(元空间)中,元空间并没有处于堆内存上,而是直接占用的本地内存(NativeMemory)。
  • 元空间的本质和永久代类似,都是对JVM规范中方法区的实现。
  • 不过元空间与永久代之间最大的区别在于:元空间并不在虚拟机中,而是使用本地内存。
  • 元空间的大小仅受本地内存限制,可以通过以下参数来指定元空间大小:
    • -XX:MetaspaceSize,初始空间大小,达到该值就会触发垃圾收集进行类型卸载,同时GC会对该值进行调整:如果释放了大量的空间,就适当降低该值;如果释放了很少的空间,那么在不超过MaxMetaspaceSize时,适当提高该值
    • -XX:MaxMetaspaceSize,最大空间,默认是没有限制的
    • -XX:MinMetaspaceFreeRatio,在GC之后,最小的Metaspace剩余空间容量的百分比,减少为分配空间所导致的垃圾收集
    • -XX:MaxMetaspaceFreeRatio,在GC之后,最大的Metaspace剩余空间容量的百分比,减少为释放空间所导致的垃圾收集
 
Java8为什么要将永久代替换成Metaspace?
  • 1、字符串存在永久代中,容易出现性能问题和内存溢出。
  • 2、类及方法的信息等比较难确定其大小,因此对于永久代的大小指定比较困 难,太小容易出现永久代溢出,太大则容易导致老年代溢出。
  • 3、永久代会为 GC 带来不必要的复杂度,并且回收效率偏低。
 
 

2.jvm的垃圾回收

说到jvm就没法不说GC(垃圾回收),我们都知道程序在运行的时候,会不断产生新的对象。那么这些对象用完之后怎么办?这里就需要垃圾回收器去把这些对象回收掉,否则就会造成OOM的情况。(此处只说一个大概的过程,各种垃圾回收器,算法此处暂时不讲)。
 
根据示意图,我们可以知道,heap主要分为 新生代和老年代,而新生代又分为eden区和两个survisor。那么垃圾回收的机制也就是在他们直接发生。当eden区的空间不足的时候,就会执行一次minor gc,这个时候会把eden区和s0区没有别回收的对象放到survisor s1区,下次执行minor gc的时候会把eden区和s1区没有别回收的对象放到survisor s0区。 也就是说s0和s1的角色每执行一次minor gc 就会交换一次,但是survisor里面的对象也不是会一直存在的,当survisor中的对象复制一次,年龄就会+1,当达到一定次数的时候,就会被放到老年代。当老年代也差不多空间要用完的时候,就要执行full gc了。
为什么要两个survisor区?是为了内存连续性,如果只是单纯的回收垃圾,会留下很多碎片空间。
 
jdk自带了一些工具来检测jvm的运行状况
 
首先,我们看看jconsole
此处可以看到堆内存,线程,类加载,CPU使用的情况。
 
可以根据选择看到不同区的内存使用情况。
 
 
 
 
 
 
 
手动执行下GC。
 
发现堆内存直线降低,然后具体看下eden区,survivor区,old区。可以看到eden区的变化与heap memory基本一致,但是survivor区是一个直线下降,old区去直线上升。说明这个时候,eden区几乎没有不能被回收的对象,而survivor区不能被回收的对象被转移到了old区。
 
 
 
底部柱状图可以直观的看到 老年代,eden区,survisor区,以及方法区(metaspace,code cache,compressed class space)使用情况
 
除了jconsole,jdk 还有另外一款工具jvisualvm
 
 
jvisualvm比jconsole更加直观一点也更加美观
这里可以直接获得hemp dump进行查看。
 
 
 

3.栈溢出和堆溢出

在java项目中,我们可能会遇到StackOverflowError和OutOfMemoryError,那么这两个错误有啥区别呢?根据上面的概念,每个线程会有自己的私有内存区域,如果线程请求的栈深度大于虚拟机允许的深度,将抛出StackOverflowError异常,如果无法申请到足够的内存,就会抛出OutOfMemoryError异常。
我们测试下StackOverflowError
public class StackErr {

    static Integer count = 0;

    public static void main(String[] args) {
        fn();
    }

    public static void fn(){
        try {
            count++;
            fn();
        }catch (Throwable e){
            System.out.println("最大深度:"+count);
            e.printStackTrace();
        }
    }

}
 
设置栈堆为1m
-Xss1m -XX:+PrintGCDetails
最大深度:7738
java.lang.StackOverflowError
    at sun.misc.Unsafe.getObjectVolatile(Native Method)
    at java.util.concurrent.ConcurrentHashMap.tabAt(ConcurrentHashMap.java:755)
    at java.util.concurrent.ConcurrentHashMap.putVal(ConcurrentHashMap.java:1028)
    at java.util.concurrent.ConcurrentHashMap.putIfAbsent(ConcurrentHashMap.java:1535)
 
设置栈堆为5m
-Xss5m -XX:+PrintGCDetails
最大深度:33033
java.lang.StackOverflowError
    at sun.misc.Unsafe.getObjectVolatile(Native Method)
    at java.util.concurrent.ConcurrentHashMap.tabAt(ConcurrentHashMap.java:755)
    at java.util.concurrent.ConcurrentHashMap.putVal(ConcurrentHashMap.java:1028)
    at java.util.concurrent.ConcurrentHashMap.putIfAbsent(ConcurrentHashMap.java:1535)
 
可以看到设置的栈堆越大,栈可以达到是深度也就越大。
 
我们测试下OutOfMemoryError
public class MemoryError {

    public static void main(String[] args) throws InterruptedException {
        System.out.print(System.getenv());
        List<Byte[]> lists = new ArrayList<>();
        for(int i=0; i<100000; i++){
            Byte[] bytes = new Byte[1*1024*1024];
            lists.add(bytes);
            Thread.sleep(100L);
        }
    }

}
 
设置最大内存100m
-Xms1m -Xmx100m -XX:+PrintGCDetails -XX:+HeapDumpOnOutOfMemoryError
通过jvisualvm可以看到内存使用在逐步上升,然后到了接近100m的时候就报错了。
 
[Full GC (Ergonomics) [PSYoungGen: 10960K->3200K(17920K)] [ParOldGen: 49119K->51899K(68608K)] 60080K->55100K(86528K), [Metaspace: 8590K->8590K(1056768K)], 0.0275027 secs] [Times: user=0.15 sys=0.01, real=0.02 secs] 
[GC (Allocation Failure) [PSYoungGen: 9578K->9264K(17920K)] 61478K->61172K(86528K), 0.0047722 secs] [Times: user=0.03 sys=0.00, real=0.01 secs] 
[GC (Allocation Failure) [PSYoungGen: 15645K->10864K(18944K)] 67553K->67172K(87552K), 0.0089717 secs] [Times: user=0.03 sys=0.01, real=0.01 secs] 
[GC (Heap Dump Initiated GC) [PSYoungGen: 15166K->10928K(18944K)] 71474K->72444K(87552K), 0.0069447 secs] [Times: user=0.03 sys=0.00, real=0.01 secs] 
[Full GC (Heap Dump Initiated GC) [PSYoungGen: 10928K->3200K(18944K)] [ParOldGen: 61516K->67909K(68608K)] 72444K->71109K(87552K), [Metaspace: 8655K->8655K(1056768K)], 0.0387764 secs] [Times: user=0.13 sys=0.01, real=0.04 secs] 
[Full GC (Ergonomics) [PSYoungGen: 10833K->10006K(18944K)] [ParOldGen: 67909K->68309K(68608K)] 78742K->78315K(87552K), [Metaspace: 8686K->8686K(1056768K)], 0.0476351 secs] [Times: user=0.20 sys=0.00, real=0.05 secs] 
[Full GC (Allocation Failure) [PSYoungGen: 10006K->10005K(18944K)] [ParOldGen: 68309K->67941K(68608K)] 78315K->77947K(87552K), [Metaspace: 8686K->8539K(1056768K)], 0.0554143 secs] [Times: user=0.24 sys=0.00, real=0.06 secs] 
[Full GC (Ergonomics) [PSYoungGen: 10005K->10005K(18944K)] [ParOldGen: 68341K->68341K(68608K)] 78347K->78347K(87552K), [Metaspace: 8541K->8541K(1056768K)], 0.0256395 secs] [Times: user=0.13 sys=0.01, real=0.02 secs] 
[Full GC (Allocation Failure) [PSYoungGen: 10005K->10005K(18944K)] [ParOldGen: 68341K->68341K(68608K)] 78347K->78347K(87552K), [Metaspace: 8541K->8541K(1056768K)], 0.0271168 secs] [Times: user=0.14 sys=0.00, real=0.03 secs] 
java.lang.OutOfMemoryError: Java heap space
Dumping heap to java_pid9925.hprof ...
Heap dump file created [160733973 bytes in 0.336 secs]
Heap
 PSYoungGen      total 18944K, used 10080K [0x00000007bdf00000, 0x00000007bfe00000, 0x00000007c0000000)
  eden space 7680K, 100% used [0x00000007bdf00000,0x00000007be680000,0x00000007be680000)
  from space 11264K, 21% used [0x00000007be680000,0x00000007be8d8060,0x00000007bf180000)
  to   space 11264K, 0% used [0x00000007bf300000,0x00000007bf300000,0x00000007bfe00000)
 ParOldGen       total 68608K, used 68346K [0x00000007b9c00000, 0x00000007bdf00000, 0x00000007bdf00000)
  object space 68608K, 99% used [0x00000007b9c00000,0x00000007bdebe848,0x00000007bdf00000)
 Metaspace       used 8558K, capacity 8808K, committed 9088K, reserved 1056768K
  class space    used 1014K, capacity 1087K, committed 1152K, reserved 1048576K
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
    at com.fengxiaotx.jdk.demo.MemoryError.main(MemoryError.java:13)
 
从日志可以看到,执行gc的时候没能清理什么对象,最后,eden区,old区都没占满,这个时候就抛出了异常。
 
如果要知道是哪个对象导致的,我们可以根据heap dump 来查看对象占用的情况。
由图可见,byte[]这个对象,吃掉了98.4%的内存,与我们的测试情符合。
 
至此,我们对jvm有了基本的了解,也能可以尝试排查一些相关问题了。
 
参考文献:
 
 
 
 
 
 
 

java
jvm

深圳SEO优化公司石家庄阿里店铺托管哪家好延安SEO按天计费海北百度竞价包年推广推荐荆门高端网站设计哪家好福永百度seo报价锦州网站改版推荐来宾百度爱采购公司天水SEO按效果付费公司北海网站优化软件报价普洱百姓网标王多少钱湘西网站关键词优化推荐肇庆网站搭建公司太原网页设计报价榆林模板推广吉林营销网站推荐玉林百度爱采购价格思茅网站推广工具报价漯河关键词按天扣费公司那曲网站搜索优化多少钱宁波百姓网标王报价酒泉建网站哪家好平凉网站定制报价辽阳网络营销哪家好保山阿里店铺托管报价泰州百搜标王报价黄南百姓网标王公司衡阳网站开发公司肇庆企业网站设计推荐绥化网站推广系统丹东模板制作多少钱歼20紧急升空逼退外机英媒称团队夜以继日筹划王妃复出草木蔓发 春山在望成都发生巨响 当地回应60岁老人炒菠菜未焯水致肾病恶化男子涉嫌走私被判11年却一天牢没坐劳斯莱斯右转逼停直行车网传落水者说“没让你救”系谣言广东通报13岁男孩性侵女童不予立案贵州小伙回应在美国卖三蹦子火了淀粉肠小王子日销售额涨超10倍有个姐真把千机伞做出来了近3万元金手镯仅含足金十克呼北高速交通事故已致14人死亡杨洋拄拐现身医院国产伟哥去年销售近13亿男子给前妻转账 现任妻子起诉要回新基金只募集到26元还是员工自购男孩疑遭霸凌 家长讨说法被踢出群充个话费竟沦为间接洗钱工具新的一天从800个哈欠开始单亲妈妈陷入热恋 14岁儿子报警#春分立蛋大挑战#中国投资客涌入日本东京买房两大学生合买彩票中奖一人不认账新加坡主帅:唯一目标击败中国队月嫂回应掌掴婴儿是在赶虫子19岁小伙救下5人后溺亡 多方发声清明节放假3天调休1天张家界的山上“长”满了韩国人?开封王婆为何火了主播靠辱骂母亲走红被批捕封号代拍被何赛飞拿着魔杖追着打阿根廷将发行1万与2万面值的纸币库克现身上海为江西彩礼“减负”的“试婚人”因自嘲式简历走红的教授更新简介殡仪馆花卉高于市场价3倍还重复用网友称在豆瓣酱里吃出老鼠头315晚会后胖东来又人满为患了网友建议重庆地铁不准乘客携带菜筐特朗普谈“凯特王妃P图照”罗斯否认插足凯特王妃婚姻青海通报栏杆断裂小学生跌落住进ICU恒大被罚41.75亿到底怎么缴湖南一县政协主席疑涉刑案被控制茶百道就改标签日期致歉王树国3次鞠躬告别西交大师生张立群任西安交通大学校长杨倩无缘巴黎奥运

深圳SEO优化公司 XML地图 TXT地图 虚拟主机 SEO 网站制作 网站优化