记录学习——JVM

学习前的问题

1.为什么要学习JVM?
按照过来人的说法:与其现在学习很多框架,还不如先学好java基础,不然我们学再多也只会一脸迷茫。要学好Java基础,最重要要学好JDK!
JDK主要包含了三部分,第一部分就是JVM,此外,第二部分就是Java的基础类库。最后,第三部分就是Java的开发工具包
2.JDK、JRE、JVM三者间的关系
JVM :英文名称(Java Virtual Machine),就是我们耳熟能详的 Java 虚拟机。它只认识 xxx.class 这种类型的文件,它能够将 class 文件中的字节码指令进行识别并调用操作系统向上的 API 完成动作。所以说,jvm 是 Java 能够跨平台的核心。
JRE :英文名称(Java Runtime Environment),我们叫它:Java 运行时环境。它主要包含两个部分,jvm 的标准实现和 Java 的一些基本类库。它相对于 jvm 来说,多出来的是一部分的 Java 类库。
JDK :英文名称(Java Development Kit),Java 开发工具包。jdk 是整个 Java 开发的核心,它集成了jre 和一些好用的小工具。例如:javac.exe,java.exe,jar.exe 等。
显然,这三者的关系是:一层层的嵌套关系。JDK>JRE>JVM

开始学习

JVM体系结构概览

一、类装载器(ClassLoader)

1、是什么?

负责加载class文件,class文件在文件开头有特定的文件标示 [1],将class文件字节码内容加载到内存中,并将这些内容转化为方法区中的运行时数据结构并且ClassLoader只负责class文件的加载,至于它是否可以运行,则由Execution Engine(执行引擎)决定

装载器工作原理

.class文件中的二进制数据读入到内存中,将其放在运行时数据区的方法区内,然后在堆区创建一个 java.lang.Class对象(Class Class<T>),用来封装类在方法区内的数据结构。类的加载的最终产品是位于堆区中的 Class对象, Class对象封装了类在方法区内的数据结构,并且向Java程序员提供了访问方法区内的数据结构的接口。

2、有几种?

虚拟机自带的加载器:
启动类加载器( Bootstrap ClassLoader):C++开发,负责加载存放在 JDK[2]\jre\lib下,能被虚拟机识别的类库(如rt.jar,所有的java.开头的类均被 BootstrapClassLoader加载)。启动类加载器是无法被Java程序直接引用的。
扩展类加载器( Extension ClassLoader):该加载器由 sun.misc.Launcher$ExtClassLoader实现,它负责加载 JDK\jre\lib\ext目录中,或者由 java.ext.dirs系统变量指定的路径中的所有类库(如javax.开头的类),开发者可以直接使用扩展类加载器。
应用程序类加载器(ApplicationClassLoader):该类加载器由 sun.misc.Launcher$AppClassLoader来实现,它负责加载用户类路径(ClassPath)所指定的类,开发者可以直接使用该类加载器。
用户自定义加载器:java.lang.ClassLoader的子类,用户自定义加载方式

层次关系

例子:

public class MyObject{
  public static void main(String[] args){
    Object object =new Object();
    System.out.println(object.getClass().getClassLoader());

    MyObject myobject =new MyObject();
    System.out.println(myobject.getClass().getClassLoader().getParent().getParent());
    System.out.println(myobject.getClass().getClassLoader().getParent());
    System.out.println(myobject.getClass().getClassLoader());
   }
}

输出结果为:(Bootstrap ClassLoader由C++编写,所以这里结果为null)


结果

3、双亲委派

如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把请求委托给父加载器去完成,依次向上,因此,所有的类加载请求最终都应该被传递到顶层的启动类加载器中,只有当父加载器在它的搜索范围中没有找到所需的类时,即无法完成该加载,子加载器才会尝试自己去加载该类。
1、当 AppClassLoader加载一个class时,它首先不会自己去尝试加载这个类,而是把类加载请求委派给父类加载器ExtClassLoader去完成。
2、当 ExtClassLoader加载一个class时,它首先也不会自己去尝试加载这个类,而是把类加载请求委派给BootStrapClassLoader去完成。
3、如果 BootStrapClassLoader加载失败(例如在$JDK/jre/lib里未查找到该class),会使用 ExtClassLoader来尝试加载;
4、若ExtClassLoader也加载失败,则会使用 AppClassLoader来加载;
5、如果 AppClassLoader也加载失败,则会报出异常 ClassNotFoundException。
双亲委派模型意义
保证沙箱安全,系统类防止内存中出现多份同样的字节码
保证Java程序安全稳定运行

二、内存结构

JVM数据区

以上图中,灰颜色(Java栈,本地方法栈,程序计数器)是运行时线程私有的内存区域,且不存在垃圾回收;而橙颜色(方法区、堆)是所有线程共享的内存区域

1、本地方法栈(Native Method Stack)

本地方法接口的作用是融合不同的编程语言为Java所用,初衷是融合C/C++程序,Java诞生时在C/C++的淫威下专门开辟了一块区域处理标记为native的代码,它的具体做法是Native Method Stack登记native方法,在Execution Engine执行时加载native libraries。
例如线程调用start方法时,要调用底层操作系统,在Thread类的源码中有以下代码:只有方法声明,没有方法实现

private native void start0();

2、程序计数器(Program Counter Register)

Register在计算机英语中指寄存器,因此程序计数器也可以叫PC寄存器
每一个线程都有一个PC寄存器,是线程私有的,它就是一个指针,指向方法区中的方法字节码(用来存储指向下一条指针的地址),由执行引擎读取下一条指令,是一个非常小的内存空间。
如果执行的是一个Native方法,那么这个计数器是空的。
此内存区域是唯一一个在Java虚拟机规范中没有规定任何内存溢出错误(OutOfMemoryError)情况的区域。

3、Java栈(Java stack)【栈管运行,堆管存储】

栈也叫栈内存,主管Java程序的运行,在线程创建时创建,它的生命周期也是跟随线程的生命周期,线程结束栈内存就释放,是线程私有的栈不存在垃圾回收问题;8种基本类型的变量+对象的引用变量+实例方法都在函数的栈内存中分配
例:int+Person+add和main

public class Test{
    public int add(int x,int y)
    {
          int result = -1;
          result =x+y;
          return result;
    }
    public static void main(String[] args)
    {
          Person p1 = new Person();
          add(2,3);
    }
}

栈保存什么?
①本地变量:输入参数(x,y)和输出参数(result)以及方法内的变量(result,p1);
②栈操作:记录出栈、入栈操作;
③栈帧数据:包括类文件、方法等(main方法压入栈底,add方法在栈顶)
栈运行原理(java方法=栈帧)
栈中的数据都是以栈帧的格式存在,栈帧是一个内存区块,是一个有关方法和运行期数据的数据集
例如:当A方法被调用就产生一个栈帧F1,A调用B又产生栈帧F2,B调用C又产生栈帧F3·····执行完毕后,先弹出F3,再F2,再F1,符合先进后出

4、堆(heap)

堆内存

Java堆(Java Heap)是Java虚拟机所管理的内存最大的一块。Java堆是被所有线程共享的一块内存区域,在虚拟机启动时创建。此内存区域的唯一目的就是存放对象实例,几乎所有的对象实例都在这里分配内存,实例对象98%属于临时对象(Eden区分配)
堆内存物理上分为:新生+养老
新生区(Eden:From:To=8:1:1):养老区=1:2


堆内存逻辑上分为:新生+养老+永久代/元空间(对应方法区)
永久区(java7之前有)存储是一个常驻内存区域,用于存放JDK自身携带的Class,Interface的元数据,也就是运行环境必须有的类信息(rt.jar),此区域的数据不会被垃圾回收掉,关闭JVM才会释放此区域内存。

5、方法区(Method Area)

实际而言,方法区与堆一样,是供各线程共享的运行时的内存区域,它存储了每个类的结构信息(类的模板Class),用于储存虚拟机加载的:类信息+普通常量+静态常量+编译器编译后的代码。虽然JVM规范将方法区描述为堆的一个逻辑部分,但它却有一个别名叫Non-Heap(非堆),目的就是要和堆分开。
永久代是方法区的一个实现:对于HotSpot虚拟机,很多开发者习惯将方法区称为“永久代”,但严格本质上说两者不同,或者说使用永久代来实现方法区而已,也就是说方法区是规范,在不同虚拟机里实现是不一样的,最典型是永久代(PermGen space)和元空间(Metaspace)。
但实例变量[3]存在堆内存中,与方法区无关

栈+堆+方法区的交互关系
(Person p1在栈中,new Person()在堆中,Person Class在方法区)

栈+堆+方法区的交互关系

HotSpot[4]是使用指针的方法来访问对象;
reference存储的就直接是对象的地址(实例变量);
Java堆中会存放访问类元数据(也就是Class,在方法区中)的地址

三、堆参数调优

JVM调优实际上就是堆内存的调优,java堆内存默认只用本机内存的1/4,如果java所占内存不够就需要调优

引言

在java8中,永久代已经被移除,被元空间取代。

元空间与永久代的最大区别:
永久代使用JVM的堆内存,但是java8以后的元空间并不在虚拟机中而是使用本地内存。

因此,元空间的大小仅受本地内存限制。类的元数据放在native memory(本地内存),字符串池和类的静态变量放在java堆中,这样可以加载多少类的元数据不再由MaxPermSize控制,而是系统的实际可用空间来控制。

java8堆内存

1、简介

参数 意义
-Xms Java堆初始分配内存,默认为内存的1/64
-Xmx Java堆最大分配内存,默认为内存的1/4
-Xmn 新生去分配内存,默认为java堆的1/3
-XX:PermSize 永久代的初始大小,几乎不会再用
-XX:MaxPermSize 永久代的最大大小,几乎不会再用
-XX:+PrintGCDetails 输出详细的GC处理日志
public static void main(String[] args)
    {
        long maxMemory = Runtime.getRuntime().maxMemory();
        long totalMemory = Runtime.getRuntime().totalMemory();
        System.out.println("最大内存:"+(maxMemory/(double)1024)/1024+"MB");
        System.out.println("初始内存:"+(totalMemory/(double)1024)/1024+"MB");
    }

JVM运行时数据区(绿区)被抽象为Runtime对象



已知本机的可用内存为7.9G,由java堆内存的分配规律,输入以下代码,得到:
最大内存:1792.0MB(大概1/4),初始内存:121.0MB(大概1/64)

2、如何进行JVM的内存配置

以IDEA为例,Run->Edit Configurations->



有关用于元数据的空间的信息包含在堆的打印输出中


生产环境中ms一般设置成跟mx相等,避免GC和应用程序争抢内存,理论值忽高忽低,造成停顿

以下两种情况会出现堆内存溢出的错误:
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
说明出现堆内存溢出,原因有二:
①Java虚拟机的堆内存不够,可以通过-Xms、-Xmx调整
②代码创建大量对象,且长时间不能被垃圾收集器收集(存在被引用)

       //不断new对象,让GC的速度跟不上new对象的速度,养老区Full GC实在腾不出位置
        String str = "Agnes";
        while (true){
            str += str + new Random().nextInt(111)+ new Random().nextInt(222);
        }
        //new一个比java最大内存还大的对象
        byte[] bytes = new byte[11*1024*1024];

四、GC

在C++中,创建出的对象是需要手动去delete掉的。我们Java程序运行在JVM中,JVM可以帮我们“自动”回收不需要的对象,对我们来说是十分方便的。

1、Heap堆new对象过程

MinorGC过程:复制->清空->互换
①Eden、SurvivorFrom复制到SurvivorTo,年龄+1
所有对象都是在伊甸区被new出来,当伊甸区空间用完时会触发第一次GC,将Eden不再被其他对象引用的对象销毁,然后将Eden剩余对象移到幸存0区Eden->from);再次触发GC时会扫描Eden区和from区,对他们进行销毁,再把存活的对象复制到To区(如果年龄到达老年标准,复制到养老区),同时年龄+1(Eden+From->To)
②清空Eden,SurvivorFrom
此时幸存者都在To区,就要清空Eden和from区
③SurvivorTo区和SurvivorFrom区互换
最后,SurvivorTo区和SurvivorFrom区互换,原来To区成为下一次GC的From区,部分对象会在from和to区间复制来复制去,交换15次(由MaxTenuringThreshold参数决定,默认15),最终进入养老区(To<->From,则下一次GC From区有对象,To区没有,即谁空谁是To)
养老区满了就触发Major GC(FullGC),若养老区还无法清理对象,腾出位置,就会产出OOM异常。

Minor GC与Full GC的区别:

  • 普通GC(Minor GC):只针对新生代区域,因为绝大大数java对象存活率不高,所以Minor GC非常频繁,回收速度也比较快。
  • 全局GC(Full GC):发生在老年代,经常会伴随至少一次的Minor GC(不绝对),速度比Minor GC要慢10倍以上。

2、GC处理日志信息

规律:[名称:GC前内存占用->GC后内存占用[该区的内存总大小]]

[GC [PSYoungGen:274931K->10738K(274944K)] 371093K->147186K(450048K),0.0668480 secs]
[Times: user=0.17 sys=0.08, real=0.07 secs]
young GC日志解读
[Full GC [PSYoungGen: 10738K->0K(274944K)]
[ParOldGen: 136447K->140379K(302592K)] 147186K->140379K(577536K) 
[PSPermGen: 85411K->85376K(171008K)], 0.6763541 secs] 
[Times: user=1.75 sys=0.02, real=0.68 secs]

Full GC日志解读

jdk11的GC回收日志信息可以参照https://www.jianshu.com/p/f1ceb0a35240,虽然不同但也能大致看懂

3、GC的四大算法

3.1 引用计数法

每个对象有一个引用计数属性,新增一个引用时计数加1,引用释放时计数减1,计数为0时可以回收。

缺点:

  • 每次对对象赋值时都要维护引用计数器,且计数器本身也有一定的消耗;
  • 较难处理循环引用

3.2 复制算法(Copying)

年轻代中使用的是Minor GC,这种GC算法采用的是复制算法
原理:从根集合(GC Root)开始,通过遍历从from中找到存活对象,拷贝到To中;From和To交换身份。

复制算法原理

优点:

  • 没有标记和清除的过程,效率高
  • 没有内存碎片

缺点:

  • 浪费幸存者区一半空间
  • 要求对象存活率要特别低

3.3 标记清除(Mark-Sweep)

老年代 所使用的垃圾回收算法
原理:算法分标记和清除两个阶段,先标出要回收的对象,然后统一回收这些对象

标记清除算法原理

优点:

  • 节约空间

缺点:

  • 会产生内存碎片,空闲内存不连续
  • 需要两次扫描,耗时间

3.4 标记压缩/整理(Mark-Compact)

就是标记清除压缩,老年代一般是由标记清除或标记清除和标记压缩混合实现
原理:首先与标记清除一样,接着再次扫描,并往一端滑动存活对象(就是为了整理标记清除产生的碎片)


优点:

  • 没有产生碎片

缺点:

  • 需要移动对象的成本,效率低

3.5 总结(分代收集算法)

内存效率:复制算法>标记清除>标记整理(效率只是简单的时间复杂度比对)
内存整齐度:复制算法=标记整理>标记清除
内存利用率:标记整理>标记清除>复制算法

次数上频繁收集Young区,较少收集Old区,基本不动元空间

年轻代特点是区域比老年代小,对象存活率低
老年代特点是区域大,对象存活率高
新生代用复制,老年代用标清,没有最好的算法,只有最适合的算法。


  1. class文件以 0xCAFEBABE开头

  2. JDK代表JDK的安装目录,可以在cmd输入java -verbose查找所在位置

  3. 类变量也叫静态变量,也就是在变量前加了static 的变量;
    实例变量也叫对象变量,即没加static 的变量;
    类变量和实例变量的区别在于:类变量是所有对象共有,其中一个对象将它值改变,其他对象得到的就是改变后的结果;而实例变量则属对象私有,某一个对象将其值改变,不影响其他对象;

  4. 就是JDK的名字

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,098评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,213评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,960评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,519评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,512评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,533评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,914评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,574评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,804评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,563评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,644评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,350评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,933评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,908评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,146评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,847评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,361评论 2 342

推荐阅读更多精彩内容

  • ClassLoader翻译过来就是类加载器,普通的java开发者其实用到的不多,但对于某些框架开发者来说却非常常见...
    时待吾阅读 1,058评论 0 1
  • 0、前言 读完本文,你将了解到: 一、为什么说Jabalpur语言是跨平台的 二、Java虚拟机启动、加载类过程分...
    vivi_wong阅读 1,196评论 0 10
  • 1、classLoader 类加载器,将class文件加载到JVM虚拟机内存中,使得程序可以运行。通常情况下,JV...
    helloWorld_1118阅读 2,197评论 0 2
  • 转发:本篇文章已授权微信公众号 guolin_blog (郭霖)独家发布 ClassLoader翻译过来就是类加载...
    尼尔君阅读 527评论 0 1
  • 本篇文章已授权微信公众号 guolin_blog (郭霖)独家发布 ClassLoader翻译过来就是类加载器,普...
    尼尔君阅读 652评论 1 0