为了账号安全,请及时绑定邮箱和手机立即绑定

java内存模型-JMM

标签:
JVM

https://img1.sycdn.imooc.com//5bcdabfa0001bf3e05580328.jpg

蓝色是内存

执行流程

https://img1.sycdn.imooc.com//5bcdac3b0001e64102400229.jpg

1.加载类(classLoader)

会在方法区 扫描到 类结构,main方法(因为是静态的)

https://img1.sycdn.imooc.com//5bcdac520001304c01740090.jpg

2.启动main

首先创建线程,并且在内部创建一个相应的栈(每一个栈都是为线程服务的,且存在由于线程内部 ,实际的存储是分开的)

每执行到的一个方法会以一个栈针的形式放入栈中

https://img1.sycdn.imooc.com//5bcdac700001895701750199.jpg

3.emp=new Employee

实例化过程,就是创建对象,首先会在堆里创建一个Employee对象,并且会把这个内存地址赋值给emp(就是持有对这个对象的引用)

https://img1.sycdn.imooc.com//5bcdac7d00012d9a04130262.jpg

4.emp.setName("老齐");

会在栈增加setName的栈针,“老齐”是字符串是静态的所以放在方法区里,同时让name持有的对象引用到“老齐”这个字符串

    https://img1.sycdn.imooc.com//5bcdac9b0001c91005580231.jpg

执行完之后,setName就会消失,因为执行完会马上从栈中弹出

https://img1.sycdn.imooc.com//5bcdaca80001245505580221.jpg

5,emp.setAge(13);

由于13是按值引用的所以直接会被保存在emp的内存中。

https://img1.sycdn.imooc.com//5bcdacb800016b7105580151.jpg

6.dept=new Department()

在堆中创建对象,并且栈中dept做引用

https://img1.sycdn.imooc.com//5bcdacc50001697a05570160.jpg

7.dept.setDname("小卖部");

将“小卖铺”这个字符串放在静态方法区,并且将引用赋值给dname

https://img1.sycdn.imooc.com//5bcdacce0001d65405570188.jpg

8.emp.setDepartment(dept);

就是employee中的departement属性引用到了Demartment的地址


https://img1.sycdn.imooc.com//5bcdad16000185e305580237.jpg

9.emp.sayJoke("一言不合....");

首先创建sayJoke 的栈针,sayJoke中也调用了getName 方法,所以同样创建栈针,在方法区创建字符创“一言不合.....”,同时sayJoke也有一个打印的过程,所以创建println的栈针,并且生成新字符串“老齐说一言不合.....” 同时println对这个字符串进行引用

https://img1.sycdn.imooc.com//5bcdad290001616305580221.jpg

方法执行完,首先ptintln 会被释放,之后是getName,最后是sayJoke

10.方法执行完成

main执行完,所以栈针消失。

此时栈中就没有任何东西,此时线程也就销毁了。

https://img1.sycdn.imooc.com//5bcdad3a00015a2505580258.jpg

剩下的就垃圾回收的工作了。

java 堆内存模型与垃圾收集

java堆内存模型

https://img1.sycdn.imooc.com//5bcdad94000113b108450484.jpg

简化理解

l  新生代:刚刚创建的对象,所以有可能存在许多的垃圾对象,所以应该优先被回收。

l  老年代:经过很多次清理之后你发现该对象依然有用,这个叫老年代

l  永久代:intern()方法入池的对象实际就在永久代当中,永久带是个bug,永久带不会被回收。除非jvm 崩溃,所以在jdk1.8之后将其更换为元空间(就是电脑的直接内存,假如电脑有100G内存空间,有80g给了堆内存,那么剩下20G都可以给元空间)。

https://img1.sycdn.imooc.com//5bcdadbc00013dea08650497.jpg

在每一代的内存空间都会有一个伸缩区,那么该区域就可以由jvm根据空间使用情况动态扩充。当我们适当合理的设计了伸缩区的内存大小之后,就可以得到良好的性能提升,也就是说最容易的性能提升就是改变伸缩区内存大小设置。

对象创建于垃圾回收

https://img1.sycdn.imooc.com//5bcdadfe000117b309190459.jpg

1.当程序中需要产生实例化对象(new,对象克隆,反射实例化)的时候就需要内存空间的开辟,所以我们需要申请新的内存空间。

2.新对象申请的空间默认都在伊甸园区,先判断是否有空间,有直接开辟新空间,此时不会发生GC处理。

3如果没有空间(伊甸园区内存空间不足),此时触发MinorGc对伊甸园区无用对象进行回收,回收完成后继续判断是否有空间可以容纳下新对象,如果可以容纳就开辟新空间。

4如果即使执行MinorGc后依然没有可回收对象,这个时候将继续判断存活区是否有空间(存活区与伊甸园区比例1:1:8),如果存活区有空余空间,则将活跃的伊甸园区存活对象保存到存活区,此时伊甸园区可以腾出部分空间(非常小)来,共新对象使用。

5 此时存活区也是满的,则继续向老年代进行空间申请,首先判断老年代空间是否空余,如果有空余则将存活区活跃对象保存到老年代,而后存活区得到空间释放,伊甸园区也得到空间释放,则空间申请成功。

6 如果老年代也是满的,就会执行Full GC(完全gc,magor GC) ,进行老年代内存释放,如果释放成功,则进行对象空间申请,

7若果释放不成功,这报异常oom(outofmemory error)

面试题:自动GC什么时候出发?

答:GC出发有两中,

MinorGc----发生在年轻代内存空间不足时,释放年轻代中不活跃对象。

Full GC----发生在老年代内存空间不足时。如果触发了Full GC后空间依然不足,则会产生outofmemoryerror

java堆内存调整参数

频繁的GC一定会影响我们的性能,如何再能不频繁的发生GC那?

示范:首先取得当前可用内存空间量。

https://img1.sycdn.imooc.com//5bcdaf930001c80505580285.jpg

在整个分配给jvm堆内存使用之中,发现total--表示用户最大可用,max-total才表示伸缩区,每一块内存中都有一个伸缩区概念,如果给用户使用的内存空间留有伸缩区会造成以下一种情况:如果可能内存不足,是否伸缩区有空间,而后部分开辟伸缩区空间。(不够开辟,多了要回收,这个过程会造成性能下降。)。

https://img1.sycdn.imooc.com//5bcdb631000186e808380462.jpg

如果取消伸缩区,想办法让初始内存就是最大内存,这样就可以实现jvm性能调整,避免重复的内存操作,可以让整个代码执行速度上升。


在整体参数中提供了一个GC处理详情的问题:-xx:+printGCDetails 会打印gc的处理过程

示范:

https://img1.sycdn.imooc.com//5bcdb6aa0001308404770068.jpg

https://img1.sycdn.imooc.com//5bcdb6b00001ff4d05580252.jpg

以上将程序处理gc的过程进行记录,我们以两条为例:

 1.年轻代的gc处理

https://img1.sycdn.imooc.com//5bcdb6cd00014e9905580107.jpg

2.老年代的gc处理

https://img1.sycdn.imooc.com//5bcdb6d8000116d005580185.jpg

3.会详细显示出内存使用情况

https://img1.sycdn.imooc.com//5bcdb6f600017c8b05580121.jpg

ps:此时内存状态只取得了供开发者观察的,但如果是项目运维人员是看不见这些内容的,如果想看见就必须使用一些监控程序,java有两类监控程序:

jdk安装目录下

https://img1.sycdn.imooc.com//5bcdb7040001e89205570252.jpg

这个是图形化监控(不好用)

https://img1.sycdn.imooc.com//5bcdb71300015b6005570185.jpg

3248是进程pid

https://img1.sycdn.imooc.com//5bcdb72100014c7e05580412.jpg

https://img1.sycdn.imooc.com//5bcdb7280001af5605580435.jpg

年轻代

分为两个区域,伊甸园区和存活区

https://img1.sycdn.imooc.com//5bcdb7500001921208990428.jpg

 所有新创建对象都放在伊甸园区,并且有个minorGC的操作处理,而且经过多次minorGC处理后依然被保留下来的对象就认为该对象不应该被回收,则将此对象保存到存活区。

存活区分两类

         存活0区(S0,FromSpace)

         存活1区(S1,ToSpace)

      这两个存活区主要是负责对象的晋级(向老年代),这两个存活区有一块是专门负责对象回收的,所以有一块内存空间总是空的。

https://img1.sycdn.imooc.com//5bcdb7940001f71309230425.jpg

在进行GC之前一定要先发生一次全对象的扫描处理,通过扫面才能知道哪些是可回收对象。

判断是否有空间创建对象的算法

https://img1.sycdn.imooc.com//5bcdb7b400016c7005580192.jpg

在java中堆中是线程共享的,多线程可能造成判断失误,所以有下面的改进算法。

https://img1.sycdn.imooc.com//5bcdb7c4000193aa05580228.jpg

将伊甸园分为多个数据块,每个数据块在用BTP技术,解决了多线程问题,但同时也产生了碎片问题。

  年轻代中还是可以通过参数进行控制的(以下参数都不建议修改,之作了解)

https://img1.sycdn.imooc.com//5bcdb7dd0001223405580222.jpg

其实最有用的部分就在于线程大小的分配,但又不能轻易修改,同时由于年轻代算法问题,理论上你的程序不可能无限接收线程(用户)

老年代

 一个对象要想真正活到老年代实在是太难了,以为程序属于多线程访问,所有业务对象都是针对于线程操作的,那么所有线程在整体操作过程中时间是非常短的。那么这些对象往往都在年轻代中开辟,很少有能跑到老年代中。

https://img1.sycdn.imooc.com//5bcdb8080001660905580168.jpg

标记清除算法(性能好但有碎片)

https://img1.sycdn.imooc.com//5bcdb8240001dd4905580241.jpg

标记压缩算法(性能差但没有碎片)

https://img1.sycdn.imooc.com//5bcdb83100014d1f05580240.jpg

老年代参数

https://img1.sycdn.imooc.com//5bcdb8510001f96805580188.jpg

  默认情况下,所有对象都会通过年轻代创建,经过多次gc后会放入老年代,如果有些对象的内容提别庞大。那么久建议不经过年轻代直接在老年代创建

    一般良好的程序开发是不可能出现上面情况的(最有可能是分页查找,如果不在数据库分页而是把数据程序中用算法分页)。

永久带(1.8之前被废除了)

永久带在1.8之前是bug存在,其本质在于该区域中的对象不会被回收。当时简单理解方法区就是永久代。

https://img1.sycdn.imooc.com//5bcdb8730001855505580245.jpg

https://img1.sycdn.imooc.com//5bcdb87b00013a6a05580131.jpg

元空间(永久代的替代品)

 1.8以后开始正式取消了永久代,取而代之就是元空间, 就是本机的物理内存,器作用和永久代相同。但元空间和永久代最大区别在:元空间用的物理内存(受本机物理内存的限制),而永久代是jvm 的内存空间本身收到jvm的限制。(其实他们主要是保存那些基本上不会被清空的操作的)

元空间参数(调整意义不大)

https://img1.sycdn.imooc.com//5bcdb89200016f5805580246.jpg

https://img1.sycdn.imooc.com//5bcdb89800016a7505580112.jpg

 对于OOM的错误信息,可能分为:

堆内存溢出(java heap space)---往往出现在fullgc之后,

元空间溢出(metaspace)-----分配物理内存不足,或者数据量高于物理内存,

永久代溢出(permGen space)----一个方法中出现内存溢出.

垃圾收集策略

  jvm 会自己选择合适的垃圾收集策略,而用户也可以设置(不建议)

垃圾收集一定要分两个空间考虑:年轻代和老年代。

下面是可用GC方式

https://img1.sycdn.imooc.com//5bcdb8d10001c52605580299.jpg

同一种垃圾回收策略,有可能会根据出发内存代不同而有不同效果,

https://img1.sycdn.imooc.com//5bcdb8e70001ef7405580230.jpg

https://img1.sycdn.imooc.com//5bcdb8f300018aab05580245.jpg

一个GC的操作,需要多个线程共同完成, 一个负责扫描出所有不用的内存对象,另一个线程负责对象的复制操作

https://img1.sycdn.imooc.com//5bcdb92d0001d48005580232.jpg

 并行回收只是处理年轻代,而并行GC需要与老年代的gc结合

老年代:

https://img1.sycdn.imooc.com//5bcdb9410001ce6005580246.jpg

https://img1.sycdn.imooc.com//5bcdb94d0001ab8005580285.jpg

与串行相对,所有操作多了多线程的支持。但是这样暂停时间就会减少。

 

目前最好的gc模式 就是并行gc(cms)

https://img1.sycdn.imooc.com//5bcdb95f0001f1ca05580280.jpg

https://img1.sycdn.imooc.com//5bcdb9700001799905580319.jpg

常用策略

https://img1.sycdn.imooc.com//5bcdb97b0001976805580044.jpg

G1收集器

很多GC收集策略虽然提供了,但是依然会有一些问题存在,原因在于内存大了,什么策略都废了。java 在10多年前就已经意识到此类问题,与是提出一个新的垃圾收集器(没广泛应用),就叫G1收集器。

https://img1.sycdn.imooc.com//5bcdb99c0001d43205580192.jpg

https://img1.sycdn.imooc.com//5bcdb9a30001ef7205580259.jpg

高并发高可用高性能 是现在程序设计的主流思路

  此时安G1实现方案相当于将所有子内存区域合并在一起,也不在进行任何区分,就相当于所有内存区域都可以按照同一方式进行规划处理,G1最大特点就是避免了全内存扫描

https://img1.sycdn.imooc.com//5bcdb9be000191d505580247.jpg

https://img1.sycdn.imooc.com//5bcdb9c50001a78a05570271.jpg

简单说就是年轻代数据会直接放到老年代中(当然中间要经过存活区),老年代不够就直接创建,没有空间了会出发G1垃圾回收策略,其实就是标记回收。

https://img1.sycdn.imooc.com//5bcdb9d00001cbcd05580247.jpg

在整个G1进行标记和清理的时候,是按照区域完成,不影响其他区域的执行,除此之外和cms执行非常类似。

如何使用G1收集器

必须用户自己指定

https://img1.sycdn.imooc.com//5bcdb9e60001fe6905580267.jpg

https://img1.sycdn.imooc.com//5bcdb9ec0001f11705580094.jpg

G1垃圾回收的处理性能一定要比传统gc处理要高。

    

java引用类型

l  强引用(Strong Reference) ,即使多次gc回收,即使jvm内存已经不够用了,该引用继续抢占

l  软引用(soft Reference),当内存空间不足时,可以回收次内存空间,如果充足则不回收,一般可以用于完成一些缓存的处理操作。

l  弱引用(Weak Reference),不管内存是否紧张,只要一出现gc处理,则立即回收。

幽灵引用(Phanton Reference),和没有引用是一样的。

强引用

   强引用是java默认支持的引用类型,也就是说在引用的处理期间即使出现gc,即使内存不足,该引用数据也不会回收。

    https://img1.sycdn.imooc.com//5bcdba29000188f205580231.jpg

在之前所编写的所有操作都是强引用,而强引用只有全部对象都断开连接之后,才可能成为垃圾。

     强引用并不是造成OOM的关键性因素,正常讲,每一个用户(线程)操作完之后该对象都可以很容易的被回收。

软引用

内存不足才进行gc空间释放,但是要想使用软引用必须单独使用特殊的处理类

https://img1.sycdn.imooc.com//5bcdba570001739105580329.jpg

https://img1.sycdn.imooc.com//5bcdba600001440c05580301.jpg

 与强引用相比,最大的特点在于:软引用中保存的内容如果在内存富裕时继续保留,内存不足会作为第一批的丢弃着,在开发的时候软引用能够实现高速缓存。

弱引用

最大特点就是一旦发生gc,则保存内容立即释放。

https://img1.sycdn.imooc.com//5bcdba87000168ed05580296.jpg

https://img1.sycdn.imooc.com//5bcdba900001be3405580278.jpg

一旦有gc 就需要释放,在类集里有一个与若引用功能相似的Map集合。

https://img1.sycdn.imooc.com//5bcdba9f0001b39405580305.jpg

WeakHashMap 它属于弱引用的一个实现

https://img1.sycdn.imooc.com//5bcdbaab00012da205580249.jpg

好处:最大的好处是可以保存一些共享数据,这些共享数据如果长时间不使用,可以将其清空。

引用队列

 如果想知道引用队列,则首先必须知道对象的引用强度。按现在理解:强引用>软引用>弱引用。

https://img1.sycdn.imooc.com//5bcdbac5000166b105580280.jpg

通过以上图像分析可以发现,如果想找到对象5,一共有两条对象可及性方案,

l  1--》5(强引用+软引用)

l  2--》6(强引用+弱引用)

在以上路径上1--5是最强引用,但本身存在有一个软引用,所以对象5对整个程序来说就是软可及对象

 

在java中如果某一个对象不属于强引用,那么就需要有一个专门的机制来清除那些不具有存在价值的对象(强引用对象才有价值),所以这是保存太多的无价值对象就会造成内存泄漏,那么我们为此专门提供有一个引用队列,当某一个对象被垃圾回收后,则该对象会被保存在引用队列中。

 

例子

https://img1.sycdn.imooc.com//5bcdbad4000151c905580257.jpg

此时没发生gc,所以队列是空的

https://img1.sycdn.imooc.com//5bcdbade000130a705580271.jpg

幽灵引用(虚引用)

什么都不保存,但是看起来像是保存了一样。

https://img1.sycdn.imooc.com//5bcdbaef000150b205580285.jpg

https://img1.sycdn.imooc.com//5bcdbaf40001e4fb05580231.jpg

https://img1.sycdn.imooc.com//5bcdbafe0001ad4a05580285.jpg

 幽灵引用直接就把要保存的东西保存到引用队列之中了。就是你希望发生引用,但又不希望引用占用空间,就用幽灵引用。


点击查看更多内容
TA 点赞

若觉得本文不错,就分享一下吧!

评论

作者其他优质文章

正在加载中
  • 推荐
  • 评论
  • 收藏
  • 共同学习,写下你的评论
感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
意见反馈 帮助中心 APP下载
官方微信

举报

0/150
提交
取消