Java 中级 学习笔记 1 JVM的理解以及新生代GC处理流程
写在最前
从毕业到现在已经过去了差不多一年的时间,工作还算顺利,但总是离不开CRUD ,我觉得这样下去肯定是不行的,温水煮青蛙,势必有一天,会昏昏沉沉的迷失在温水里。所以,需要将之前学习JAVA 当中一些中高级部分的知识需要进行学习和记录,并将其整理博客,一起成长,一起努力。
JVM
JAVA虚拟机在运行的时候,会给所有的变量、以及实例对象等分配内存区域,当然这一块内存区域是在Java 虚拟机上分配的,虚拟机的内存。
这里先来了解一个区别,就是JVM 与 Hotspot
JVM 可以理解为是一种标准,就好像我们JAVA 里面定义的接口一样,它是一个笼统的概念。
而Hotspot则具体是JVM 的一种具体实现,它由SUN 公司开发,并且在Open-jdk 与 sun-jdk 当中包含的,都是Hotspot 虚拟机。
特点:JIT 即时编译、热点探测等
参考:https://www.cnblogs.com/baxianhua/p/9528192.html
先从JVM内存分布区分为:线程共享区以及线程私有区
线程内存共享区
- 堆 Heap
- 方法区/永久带
线程的内存共享区域包含堆和方法区,方法区也可以叫做是永久带,其实是HotSpot VM 将堆里面一块关于永久代的内存区域用来实现方法区,为什么要这样做呢?其实虚拟机的垃圾回收机制希望也控制这一块内存的装载与卸载,但是手头有不想单独给他独立划分一个出去,那就从堆里面拿出用来放永久代的内存区域用来实现方法区即可。
堆 heap (线程共享)
堆作为JVM 虚拟机最大的共享内存区域,用来存放实例对象以及数组等,也是垃圾收集器GC 进行的重要区域。这里一会儿会涉及到一个关于分代回收的垃圾处理算法。
首先来了解一小部分,比如我们平时使用new 关键字进行对象的实例化的时候,就会在堆里面开辟一块内存,用来存放我们实例后的对象。
当代JVM 大都采用分代回收的算法,按照GC的角度,又可以将堆细分为新生代和老年代
新生代占据堆的1/3,而老年代占据堆内存的2/3
新生代的划分 Eden/FromSurvivor/To Survivor
新生代Eden区
这里是每一个对象出生的地方,每当新分配一个对象的时候,若出现Eden区域内存不足,则会触发MinorGC
负责清理新生代的内存。
Minor GC
从年轻代空间(包括 Eden 和 Survivor 区域)回收内存被称为 Minor GC。这一定义既清晰又易于理解。但是,当发生Minor GC事件的时候,有一些有趣的地方需要注意到:
- 当 JVM 无法为一个新的对象分配空间时会触发 Minor GC,比如当 Eden 区满了。所以分配率越高,越频繁执行 Minor GC。
- 内存池被填满的时候,其中的内容全部会被复制,指针会从0开始跟踪空闲内存。Eden 和 Survivor 区进行了标记和复制操作,取代了经典的标记、扫描、压缩、清理操作。所以 Eden 和 Survivor 区不存在内存碎片。写指针总是停留在所使用内存池的顶部。
- 执行 Minor GC 操作时,不会影响到永久代。从永久代到年轻代的引用被当成 GC roots,从年轻代到永久代的引用在标记阶段被直接忽略掉。
- 质疑常规的认知,所有的 Minor GC 都会触发“全世界的暂停(stop-the-world)”,停止应用程序的线程。对于大部分应用程序,停顿导致的延迟都是可以忽略不计的。其中的真相就 是,大部分 Eden 区中的对象都能被认为是垃圾,永远也不会被复制到 Survivor 区或者老年代空间。如果正好相反,Eden 区大部分新生对象不符合 GC 条件,Minor GC 执行时暂停的时间将会长很多。
所以 Minor GC 的情况就相当清楚了——每次 Minor GC 会清理年轻代的内存。
FromSurvivor
上一次GC 清理过后的幸存者,分配到此块区域
To Survivor
在Minor GC 清理过程中的幸存者,移到该区域。
新生代Minor GC 回收过程
回收算法:复制算法
回收过程:复制-》清空-》互换
- 首先将Eden 区和From 区域存活的对象进行复制到To区域
- 将原有的Eden区和From 区域进行清空
- 最后将From 域To 区域进行一个互换,这时候To区域将成为下一次GC过程当中的From 区域
老年代区域
从上面学习的内容了解,老年代的对象大多都来自于对象年龄的+1导致对象年龄超过新生代,从而防止到老年代的位置,也有一部分来自于
新生代,新生代在初始化实例的时候,若遇到内存不足,可直接将对象内存分配到老年代。
老年代的对象基本上都很稳定,因此,在老年代工作的GC : Major GC
Major GC 不会频繁的进行内存清理。在Major GC 进行前,至少有一次Minor GC 进行了新生代的清理工作,导致对象年龄增加而在老年代有了其对象。
清理算法:标记清除法
MajorGC 采用标记清除算法:首先扫描一次所有老年代,标记出存活的对象,然后回收没
有标记的对象。 MajorGC 的耗时比较长,因为要扫描再回收。 MajorGC 会产生内存碎片,为了减
少内存损耗,我们一般需要进行合并或者标记出来方便下次直接分配。当老年代也满了装不下的
时候,就会抛出 OOM(Out of Memory)异常。
方法区、永久代(线程共享)
方法区用来存放一些常量、静态变量、以及JAVA 虚拟机加载类以后类的一些信息都是存放在方法区的。
方法区还存在一个叫做运行时常量池。
GC 不会在主程序运行期对永久区域进行清理。所以这
也导致了永久代的区域会随着加载的 Class 的增多而胀满,最终抛出 OOM 异常。
运行时常量池
常量池作为方法区的一部分,在Class 文件被java虚拟机加载的时候,一个类对象包含的字段、方法、还有一项就是常量池,常量池用于存放编译时期产生的各种字面量和符号引用。
class 文件被加载后,所产生的内容就会被存放到方法区的常量池。
线程内存私有区
线程私有区域的内存生命周期与线程的生命周期是一致的。依赖用户线程启动则分配内存,线程运行结束则回收内存。
在Hotspot VM 当中,每个用户的线程与操作系统的线程直接映射,这一部分的内存跟随本地线程的存活
虚拟机栈(线程私有)
虚拟机栈是描述JAVA方法在运行时候的内存模型,而每个线程在虚拟机栈里面都有属于自己的栈帧,栈帧随着方法执行被创建,
创建入栈,执行完毕方法后出栈,栈帧被销毁。
一个栈帧包含有:方法执行过程产生的局部变量表,以及操作数栈,动态链接,方法出口等。
随着方法创建而创建入栈,随着方法运行完毕则销毁。
本地方法栈(线程私有)
本地方法栈主要为本地Native方法服务,而与之类似的虚拟机栈则是为Java 方法提供服务
程序计数器(线程私有)
程序计数器是一块较小的内存空间,
每个线程都有自己的程序计数器,计数器可以理解为是一个储存每个线程在执行过程中记录当前执行的行号以及
Java 方法在执行过程中虚拟机字节码指令的地址。
Java 8 与元空间
现在基本上所有的开发都一般以JDK 1.8开始,我们需要了解一下JAVA8 当中的元空间。
其实元空间的理解与在JVM当中的方法区/永久代类似。永久代在JAVA8当中被移除
不同在于:元空间不在虚拟机内,而在用户机器的内存上开辟。
JVM在加载类的时候,将类的元数据(字段、名称、类型、长度)等放入本地内存。
而将字符串常量以及类的静态变量等信息放入JVM 堆中形成字符串常量池。
好处:不会再因为永久代从不会被GC进行清理导致的OOM错误等。
容易混淆的地方:
常量池、运行时常量池、字符串常量池
在Class文件中除了有类的版本【高版本可以加载低版本】、字段、方法、接口等描述信息外,还有一项信息是常量池(Constant Pool Table)【此时没有加载进内存,也就是在文件中】,用于存放编译期生成的各种字面量和符号引用。
下面对字面量和符号引用进行说明
字面量
字面量类似与我们平常说的常量,主要包括:
- 文本字符串:就是我们在代码中能够看到的字符串,例如String a = “aa”。其中”aa”就是字面量。
- 被final修饰的变量。
符号引用
主要包括以下常量:
- 类和接口和全限定名:例如对于String这个类,它的全限定名就是java/lang/String。
- 字段的名称和描述符:所谓字段就是类或者接口中声明的变量,包括类级别变量(static)和实例级的变量。
- 方法的名称和描述符。所谓描述符就相当于方法的参数类型+返回值类型。
2.2 运行时常量池
我们知道类加载器会加载对应的Class文件,而上面的class文件中的常量池,会在类加载后进入方法区中的运行时常量池【此时存在在内存中】。并且需要的注意的是,运行时常量池是全局共享的,多个类共用一个运行时常量池。并且class文件中常量池多个相同的字符串在运行时常量池只会存在一份。
注意运行时常量池存在于方法区中。2.3 字符串常量池
看名字我们就可以知道字符串常量池会用来存放字符串,也就是说常量池中的文本字符串会在类加载时进入字符串常量池。
那字符串常量池和运行时常量池是什么关系呢?上面我们说常量池中的字面量会在类加载后进入运行时常量池,其中字面量中有包括文本字符串,显然从这段文字我们可以知道字符串常量池存在于运行时常量池中。也就存在于方法区中。
不过在周志明那本深入java虚拟机中有说到,到了JDK1.7时,字符串常量池就被移出了方法区,转移到了堆里了。
那么我们可以推断,到了JDK1.7以及之后的版本中,运行时常量池并没有包含字符串常量池,运行时常量池存在于方法区中,而字符串常量池存在于堆中。
学以致用
通过上面的了解。已经大致了解到常量池的一些相关内容了。最后再提一下。以及新手很难立即的String 这个引用类型的一些操作中涉及到的内容
String intern()
Intern() 方法算是很常见但却很容易忽略的一个关键方法,在JDK的文档中,它是这样定义的。
若池里面存在与之内容相同的字符串,则返回常量池那个对象的引用,若不存在,则创建一个,并返回此对象在池里面的引用地址。
String a = new String("ab");
String b = new String("ab");
String c = "ab";
String d = "a" + "b";
String e = "b";
String f = "a" + e; System.out.println(b.intern() == a);//false
System.out.println(b.intern() == c);//true
System.out.println(b.intern() == d);//true
System.out.println(b.intern() == f);//false
System.out.println(b.intern() == a.intern());//true
就按照这个例子,和大家简单的聊一聊。
1、2行分别用new 关键字创建了对象,此时的对象存在于堆中
3行直接用双引号声明的对象“ab” 则首先会添加到常量池里面。String 类型的c变量指向位于字符串常量池的"ab";
4行通过+号将直接声明的"a"和“b”进行了一个拼接,这里需要着重说明一下:
JAVA 在编译的时候就会把类似“aaa”+"bbb"的代码直接优化成:“aaabbb”
所以4行这里进行了所谓的拼接,其实编译后还是“ab”,当然,第三行执行完后,常量池已存在“ab” 那么String 类型的变量d 指向常量池“ab”
5、6行通过先定义一个“b”存放到字符串常量池后,通过拼接变量的方式,其实这个对象最后是创建在了堆里面而不会进入常量池。
答案解析
8行通过b变量执行intern() 方法后,去常量池找,找到返回的其实是c的内存地址。则肯定和a(new 出来的)内存地址不相等。false
9行就不用说了,c与c比较,肯定true
10行 d其实指向的本来就是c的内存地址。true
11我们知道一个字符串常量+一个字符串变量得到的一个新对象其实是在堆里面出现的,肯定不会相同。false
12最后一个想必不用多说,两个都拿出的是c的地址。true
小结
通过今天的学习,掌握JVM 当中内存的分布关系以及堆这个最重要的内存共享区域内的对象迭代过程,以及从Class 文件被编译,编译后形成常量池,再到Class文件被
JVM加载到内存后,将对象的信息存入方法区,而方法区域存在的运行时常量池。也就为了存放对象的字面量、以及符号引用
再到JDK8以后,将运行时常量池就放到堆里面了。
参考:
常量池相关内容: https://www.cnblogs.com/gxyandwmm/p/9495923.html
字符串的拼接:https://www.cnblogs.com/nianzhilian/p/8810966.html
intern() https://www.runoob.com/java/java-string-intern.html
Java 中级 学习笔记 1 JVM的理解以及新生代GC处理流程的更多相关文章
- Java 中级 学习笔记 2 JVM GC 垃圾回收与算法
前言 在上一节的学习中,已经了解到了关于JVM 内存相关的内容,比如JVM 内存的划分,以及JDK8当中对于元空间的定义,最后就是字符串常量池等基本概念以及容易混淆的内容,我们都已经做过一次总结了.不 ...
- [原创]java WEB学习笔记27:深入理解面向接口编程
本博客为原创:综合 尚硅谷(http://www.atguigu.com)的系统教程(深表感谢)和 网络上的现有资源(博客,文档,图书等),资源的出处我会标明 本博客的目的:①总结自己的学习过程,相当 ...
- java之jvm学习笔记十三(jvm基本结构)
java之jvm学习笔记十三(jvm基本结构) 这一节,主要来学习jvm的基本结构,也就是概述.说是概述,内容很多,而且概念量也很大,不过关于概念方面,你不用担心,我完全有信心,让概念在你的脑子里变成 ...
- Java虚拟机学习笔记——JVM垃圾回收机制
Java虚拟机学习笔记——JVM垃圾回收机制 Java垃圾回收基于虚拟机的自动内存管理机制,我们不需要为每一个对象进行释放内存,不容易发生内存泄漏和内存溢出问题. 但是自动内存管理机制不是万能药,我们 ...
- 20145213《Java程序设计学习笔记》第六周学习总结
20145213<Java程序设计学习笔记>第六周学习总结 说在前面的话 上篇博客中娄老师指出我因为数据结构基础薄弱,才导致对第九章内容浅尝遏止地认知.在这里我还要自我批评一下,其实我事后 ...
- java多线程学习笔记——详细
一.线程类 1.新建状态(New):新创建了一个线程对象. 2.就绪状态(Runnable):线程对象创建后,其他线程调用了该对象的start()方法.该状态的线程位于可运行线程池中, ...
- 零拷贝详解 Java NIO学习笔记四(零拷贝详解)
转 https://blog.csdn.net/u013096088/article/details/79122671 Java NIO学习笔记四(零拷贝详解) 2018年01月21日 20:20:5 ...
- 尚学堂JAVA基础学习笔记
目录 尚学堂JAVA基础学习笔记 写在前面 第1章 JAVA入门 第2章 数据类型和运算符 第3章 控制语句 第4章 Java面向对象基础 1. 面向对象基础 2. 面向对象的内存分析 3. 构造方法 ...
- [原创]java WEB学习笔记95:Hibernate 目录
本博客的目的:①总结自己的学习过程,相当于学习笔记 ②将自己的经验分享给大家,相互学习,互相交流,不可商用 内容难免出现问题,欢迎指正,交流,探讨,可以留言,也可以通过以下方式联系. 本人互联网技术爱 ...
随机推荐
- 第三期 预测——Frenet 坐标
Frenet坐标 在讨论过程模型之前,我们应该提到“Frenet Coordinates”,它是一种以比传统x,y笛卡尔坐标更直观的方式表示道路位置的方式. 用Frenet坐标,我们使用变量 s和d描 ...
- oracle避免在索引列上使用IS NULL和IS NOT NULL
避免在索引中使用任何可以为空的列,ORACLE将无法使用该索引 .对于单列索引,如果列包含空值,索引中将不存在此记录. 对于复合索引,如果每个列都为空,索引中同样不存在此记录. 如果至少有一个列不为空 ...
- git比较两个版本之间的区别
查看当前没有add 的内容修改: git diff 查看已经add 没有commit 的改动 git diff --cached 查看当前没有add和commit的改动: git diff HEAD ...
- js实现点击隐藏图片
方法一: 把图片的display设为none,触发点击事件时,display变为block <style> img { width: 400px;height: 300px; displa ...
- [转]Jquery属性选择器(同时匹配多个条件,与或非)(附样例)
1. 前言 为了处理除了两项不符合条件外的选择,需要用到jquery选择器的多个条件匹配来处理,然后整理了一下相关的与或非的条件及其组合. 作为笔记记录. 2. 代码 1 2 3 4 5 6 7 8 ...
- HDU6383p1m2(二分)
补个题.. 传送门 点我 Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 131072/131072 K (Java/Others)To ...
- classpath*与classpath
classpath*:的出现是为了从多个jar文件中加载相同的文件. classpath:只能加载找到的第一个文件.
- vue-cli常用插件集合
element - 饿了么出品的Vue2的web UI工具套件 Vux - 基于Vue和WeUI的组件库 mint-ui - Vue 2的移动UI元素 iview - 基于 Vuejs 的开源 UI ...
- js 快速取整
我们要将23.8转化成整数 有哪些方法呢 比如 Math.floor( ) 对数进行向下取整 它返回的是小于或等于函数参数,并且与之最接近的整数 Math.floor(5.1) 返回值 //5 M ...
- HDU 6662 Acesrc and Travel (换根dp)
Problem Description Acesrc is a famous tourist at Nanjing University second to none. During this sum ...