蓝色是内存
执行流程
1.加载类(classLoader)
会在方法区 扫描到 类结构,main方法(因为是静态的)
2.启动main
首先创建线程,并且在内部创建一个相应的栈(每一个栈都是为线程服务的,且存在由于线程内部 ,实际的存储是分开的)
每执行到的一个方法会以一个栈针的形式放入栈中
3.emp=new Employee
实例化过程,就是创建对象,首先会在堆里创建一个Employee对象,并且会把这个内存地址赋值给emp(就是持有对这个对象的引用)
4.emp.setName("老齐");
会在栈增加setName的栈针,“老齐”是字符串是静态的所以放在方法区里,同时让name持有的对象引用到“老齐”这个字符串
执行完之后,setName就会消失,因为执行完会马上从栈中弹出
5,emp.setAge(13);
由于13是按值引用的所以直接会被保存在emp的内存中。
6.dept=new Department()
在堆中创建对象,并且栈中dept做引用
7.dept.setDname("小卖部");
将“小卖铺”这个字符串放在静态方法区,并且将引用赋值给dname
8.emp.setDepartment(dept);
就是employee中的departement属性引用到了Demartment的地址
9.emp.sayJoke("一言不合....");
首先创建sayJoke 的栈针,sayJoke中也调用了getName 方法,所以同样创建栈针,在方法区创建字符创“一言不合.....”,同时sayJoke也有一个打印的过程,所以创建println的栈针,并且生成新字符串“老齐说一言不合.....” 同时println对这个字符串进行引用
方法执行完,首先ptintln 会被释放,之后是getName,最后是sayJoke
10.方法执行完成
main执行完,所以栈针消失。
此时栈中就没有任何东西,此时线程也就销毁了。
剩下的就垃圾回收的工作了。
java 堆内存模型与垃圾收集
java堆内存模型
简化理解
l 新生代:刚刚创建的对象,所以有可能存在许多的垃圾对象,所以应该优先被回收。
l 老年代:经过很多次清理之后你发现该对象依然有用,这个叫老年代
l 永久代:intern()方法入池的对象实际就在永久代当中,永久带是个bug,永久带不会被回收。除非jvm 崩溃,所以在jdk1.8之后将其更换为元空间(就是电脑的直接内存,假如电脑有100G内存空间,有80g给了堆内存,那么剩下20G都可以给元空间)。
在每一代的内存空间都会有一个伸缩区,那么该区域就可以由jvm根据空间使用情况动态扩充。当我们适当合理的设计了伸缩区的内存大小之后,就可以得到良好的性能提升,也就是说最容易的性能提升就是改变伸缩区内存大小设置。
对象创建于垃圾回收
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那?
示范:首先取得当前可用内存空间量。
在整个分配给jvm堆内存使用之中,发现total--表示用户最大可用,max-total才表示伸缩区,每一块内存中都有一个伸缩区概念,如果给用户使用的内存空间留有伸缩区会造成以下一种情况:如果可能内存不足,是否伸缩区有空间,而后部分开辟伸缩区空间。(不够开辟,多了要回收,这个过程会造成性能下降。)。
如果取消伸缩区,想办法让初始内存就是最大内存,这样就可以实现jvm性能调整,避免重复的内存操作,可以让整个代码执行速度上升。
在整体参数中提供了一个GC处理详情的问题:-xx:+printGCDetails 会打印gc的处理过程
示范:
以上将程序处理gc的过程进行记录,我们以两条为例:
1.年轻代的gc处理
2.老年代的gc处理
3.会详细显示出内存使用情况
ps:此时内存状态只取得了供开发者观察的,但如果是项目运维人员是看不见这些内容的,如果想看见就必须使用一些监控程序,java有两类监控程序:
jdk安装目录下
这个是图形化监控(不好用)
3248是进程pid
年轻代
分为两个区域,伊甸园区和存活区
所有新创建对象都放在伊甸园区,并且有个minorGC的操作处理,而且经过多次minorGC处理后依然被保留下来的对象就认为该对象不应该被回收,则将此对象保存到存活区。
存活区分两类
存活0区(S0,FromSpace)
存活1区(S1,ToSpace)
这两个存活区主要是负责对象的晋级(向老年代),这两个存活区有一块是专门负责对象回收的,所以有一块内存空间总是空的。
在进行GC之前一定要先发生一次全对象的扫描处理,通过扫面才能知道哪些是可回收对象。
判断是否有空间创建对象的算法
在java中堆中是线程共享的,多线程可能造成判断失误,所以有下面的改进算法。
将伊甸园分为多个数据块,每个数据块在用BTP技术,解决了多线程问题,但同时也产生了碎片问题。
年轻代中还是可以通过参数进行控制的(以下参数都不建议修改,之作了解)
其实最有用的部分就在于线程大小的分配,但又不能轻易修改,同时由于年轻代算法问题,理论上你的程序不可能无限接收线程(用户)
老年代
一个对象要想真正活到老年代实在是太难了,以为程序属于多线程访问,所有业务对象都是针对于线程操作的,那么所有线程在整体操作过程中时间是非常短的。那么这些对象往往都在年轻代中开辟,很少有能跑到老年代中。
标记清除算法(性能好但有碎片)
标记压缩算法(性能差但没有碎片)
老年代参数
默认情况下,所有对象都会通过年轻代创建,经过多次gc后会放入老年代,如果有些对象的内容提别庞大。那么久建议不经过年轻代直接在老年代创建
一般良好的程序开发是不可能出现上面情况的(最有可能是分页查找,如果不在数据库分页而是把数据程序中用算法分页)。
永久带(1.8之前被废除了)
永久带在1.8之前是bug存在,其本质在于该区域中的对象不会被回收。当时简单理解方法区就是永久代。
元空间(永久代的替代品)
1.8以后开始正式取消了永久代,取而代之就是元空间, 就是本机的物理内存,器作用和永久代相同。但元空间和永久代最大区别在:元空间用的物理内存(受本机物理内存的限制),而永久代是jvm 的内存空间本身收到jvm的限制。(其实他们主要是保存那些基本上不会被清空的操作的)
元空间参数(调整意义不大)
对于OOM的错误信息,可能分为:
堆内存溢出(java heap space)---往往出现在fullgc之后,
元空间溢出(metaspace)-----分配物理内存不足,或者数据量高于物理内存,
永久代溢出(permGen space)----一个方法中出现内存溢出.
垃圾收集策略
jvm 会自己选择合适的垃圾收集策略,而用户也可以设置(不建议)
垃圾收集一定要分两个空间考虑:年轻代和老年代。
下面是可用GC方式
同一种垃圾回收策略,有可能会根据出发内存代不同而有不同效果,
一个GC的操作,需要多个线程共同完成, 一个负责扫描出所有不用的内存对象,另一个线程负责对象的复制操作
并行回收只是处理年轻代,而并行GC需要与老年代的gc结合
老年代:
与串行相对,所有操作多了多线程的支持。但是这样暂停时间就会减少。
目前最好的gc模式 就是并行gc(cms)
常用策略
G1收集器
很多GC收集策略虽然提供了,但是依然会有一些问题存在,原因在于内存大了,什么策略都废了。java 在10多年前就已经意识到此类问题,与是提出一个新的垃圾收集器(没广泛应用),就叫G1收集器。
高并发高可用高性能 是现在程序设计的主流思路
此时安G1实现方案相当于将所有子内存区域合并在一起,也不在进行任何区分,就相当于所有内存区域都可以按照同一方式进行规划处理,G1最大特点就是避免了全内存扫描。
简单说就是年轻代数据会直接放到老年代中(当然中间要经过存活区),老年代不够就直接创建,没有空间了会出发G1垃圾回收策略,其实就是标记回收。
在整个G1进行标记和清理的时候,是按照区域完成,不影响其他区域的执行,除此之外和cms执行非常类似。
如何使用G1收集器
必须用户自己指定
G1垃圾回收的处理性能一定要比传统gc处理要高。
java引用类型
l 强引用(Strong Reference) ,即使多次gc回收,即使jvm内存已经不够用了,该引用继续抢占
l 软引用(soft Reference),当内存空间不足时,可以回收次内存空间,如果充足则不回收,一般可以用于完成一些缓存的处理操作。
l 弱引用(Weak Reference),不管内存是否紧张,只要一出现gc处理,则立即回收。
幽灵引用(Phanton Reference),和没有引用是一样的。
强引用
强引用是java默认支持的引用类型,也就是说在引用的处理期间即使出现gc,即使内存不足,该引用数据也不会回收。
在之前所编写的所有操作都是强引用,而强引用只有全部对象都断开连接之后,才可能成为垃圾。
强引用并不是造成OOM的关键性因素,正常讲,每一个用户(线程)操作完之后该对象都可以很容易的被回收。
软引用
内存不足才进行gc空间释放,但是要想使用软引用必须单独使用特殊的处理类
与强引用相比,最大的特点在于:软引用中保存的内容如果在内存富裕时继续保留,内存不足会作为第一批的丢弃着,在开发的时候软引用能够实现高速缓存。
弱引用
最大特点就是一旦发生gc,则保存内容立即释放。
一旦有gc 就需要释放,在类集里有一个与若引用功能相似的Map集合。
WeakHashMap 它属于弱引用的一个实现
好处:最大的好处是可以保存一些共享数据,这些共享数据如果长时间不使用,可以将其清空。
引用队列
如果想知道引用队列,则首先必须知道对象的引用强度。按现在理解:强引用>软引用>弱引用。
通过以上图像分析可以发现,如果想找到对象5,一共有两条对象可及性方案,
l 1--》5(强引用+软引用)
l 2--》6(强引用+弱引用)
在以上路径上1--5是最强引用,但本身存在有一个软引用,所以对象5对整个程序来说就是软可及对象
在java中如果某一个对象不属于强引用,那么就需要有一个专门的机制来清除那些不具有存在价值的对象(强引用对象才有价值),所以这是保存太多的无价值对象就会造成内存泄漏,那么我们为此专门提供有一个引用队列,当某一个对象被垃圾回收后,则该对象会被保存在引用队列中。
例子
此时没发生gc,所以队列是空的
幽灵引用(虚引用)
什么都不保存,但是看起来像是保存了一样。
幽灵引用直接就把要保存的东西保存到引用队列之中了。就是你希望发生引用,但又不希望引用占用空间,就用幽灵引用。