引用类型
- 强引用:
Person p=new Person();
一般使用的都是强引用
- jvm不会回收强引用
- 如果内存不足则oom
- 如果想切断强引用:
Person p=null
- 软引用:SoftReference
- 在内存不足时,抛出oom前会回收软引用.通常被用来缓存一些重要但是非必须的数据
软引用:如果内存充足,是不会回收的。如果内存不足会将较早的对象回收
SoftReference[] t2=new SoftReference[count];
for (int i=0;i<count;i++){
t2[i]=new SoftReference(new TestObj("ming"+i));
}
System.out.println(t2[9000].get());//能正常获取
System.out.println(t2[0].get());//内存不足被回收了
- 弱引用: WeakReference
- 发现弱引用就会被回收(执行
System.gc()
)
- 发现弱引用就会被回收(执行
弱引用:存在到下一次垃圾回收之前,无论内存是否充足都会被回收
int weakReferenceLength=100;
WeakReference[] t3=new WeakReference[weakReferenceLength];
for (int i=0;i<weakReferenceLength;i++){
t3[i]=new WeakReference(new TestObj("ming"+i),queue);
if (i==1){
// System.gc();
System.out.println(t3[1].get());//能正常获取
}
}
System.out.println(t3[1].get());//内存充足,但可能已经被回收了
- 虚引用:用来探测对象有没有被回收
- 任何时刻都有可能被回收
四种引用的概念:https://blog.csdn.net/cadi2011/article/details/50994496
不同引用存在的意义
- 对gc回收时机不可控的一个妥协,使得我们控制回收的时间点
- eg:用来当做缓存存放经常访问的数据,用来作为缓存存放一些不太重要的静态资源
对象存活判定
引用计数法
- 有引用时加一,没有引用的时候减一,为零时回收
- 问题:这种算法很难解决对象之间相互引用的情况
可达性分析算法
- “GC Roots”的对象作为起始点,当对象没有办法到达gc root对象,则认为不可达
- gc root对象:
- 虚拟机栈中的引用对象
- 方法区中类静态属性引用的对象
- 方法区中常量引用的对象
- 本地方法栈中引用的对象
方法区的垃圾回收
- 主要回收废弃的常量、无用的类
- 常量:如果程序中没有引用在指向该常量,则可以被回收。例如常量池中的”abc”,如果没有String对象指向“abc”则进行回收
- 无用类:1、该类的所有实例都已经回收 2、加载该类的classLoader已经回收 3、该类对应的class对象不在使用
垃圾收集算法
标记清除
- 首先标记出所有需要回收的对象,标记完成后统一回收所有被标记的对象
- 不足:标记和清除的效率都不高;清除后产生大量的碎片空间,如果分配较大的对象可能空间不够再次触发gc
复制算法
- 每次只使用一半的空间,gc时将存活对象复制到另一般内存中,再对当前的内存进行清除
- 不足:浪费内存,有一半的内存没有利用。不过这种算法的改进版被用在回收新生代中,新生代中的对象每次回收都基本上只有10%左右的对象存活,所以需要复制的对象很少,效率很高
标记整理
- 让所有存活对象都向内存一端移动,然后直接清理掉边界以外的内存
- 这种算法主要用在老年代这种不易被回收的对象上。这类对象存活率高,如果直接复制效率很低
分代收集算法
- 现在jvm主要使用的gc算法。主要就是结合复制算法(针对新生代:大批的对象死去,存活率不高)和标记整理算法(针对老年代:对象相对稳定,不需要额外的空间担保)
垃圾收集器
- 就是上面收集算法的具体实现,在HotSpot中如下:
Serial收集器
- 采用复制算法,单线程收集。收集过程中会阻塞其他的线程(可有停顿)。可以获取最高的单核效率
- Serial收集器依然是虚拟机运行在Client模式下的默认新生代收集器
https://images2015.cnblogs.com/blog/249993/201703/249993-20170308204330750-898195038.png
ParNew收集器
- ParNew收集器其实就是Serial收集器的多线程版本,除了多线程外没什么特别
- 但是它却是Server模式下的虚拟机首选的新生代收集器
Parallel Scavenge收集器
- 利用复制算法,多线程的收集内存
- 但是关注点在吞吐量(吞吐量=运行用户代码时间/(运行用户代码时间+垃圾收集时间))。适合于后台大运算量,没有太多的用户交互
Serial Old收集器
- 单线程,标记整理算法。虚拟机运行在Client模式下的默认老生代收集器
Parallel Old收集器
- Parallel Scavenge收集器的老年代版本,使用标记整理算法
- 注重吞吐量以及CPU资源敏感的场合,都可以优先考虑Parallel Scavenge收集器+Parallel Old收集器的组合
CMS
- CMS(Conrrurent Mark Sweep)收集器是以获取最短回收停顿时间为目标的收集器
G1
- 将整个Java堆划分为多个大小相等的独立区域(Region),虽然还保留有新生代和老年代的概念,但新生代和老年代不再是物理隔离的了,它们都是一部分(可以不连续)Region的集合。
- 未来可能替换掉cms
https://www.cnblogs.com/xiaoxi/p/6486852.html
新生代、老年代、永久代
- 新生代发生的GC也叫做MinorGC,MinorGC发生频率比较高,不一定等 Eden区满了才触发
- 老年代存放的都是一些生命周期较长的对象,在新生代中经历了N次垃圾回收后仍然存活的对象就会被放到老年代中。老年代满后会触发:Major GC(Full GC),对新生代、老生代进行回收。触发条件:老年代被写满、永久代(Perm)被写满和System.gc()被显式调用等。
- 永久代主要用于存放静态文件,如Java类、方法等。如果需要动态生成class,需要较大的永久代
内存划分如下
当前线程的内存
程序计数器
- 通过改变这个计数器的值来选取下一条需要执行的字节码指令。
- 在任何一个确定的时刻,一个处理器(对于多核处理器来说是一个内核)只会执行一条线程中的指令,确保在线程切换后能正常恢复
虚拟机栈
- 用于存储局部变量表、操作栈、动态链接、方法出口等
- 局部变量表需要的内存一旦分配好不能改变
- 一般程序员关注的堆栈:“堆”为java中个线程共享的堆内存,”栈”就是现在讲的虚拟机栈,或者说是虚拟机栈中的局部变量表,存放了编译期可知的各种基本数据类型或者对象引用
- 其中64位长度的long和double类型的数据会占用2个局部变量空间(Slot)
- 异常情况:
- 栈深度大于虚拟机所允许的深度,将抛出StackOverflowError异常
- 当扩展时无法申请到足够的内存时会抛出OutOfMemoryError异常
本地方法栈
- 本地方法栈则是为虚拟机使用到的Native方法服务
- 有的虚拟机(譬如Sun HotSpot虚拟机)直接就把本地方法栈和虚拟机栈合二为一。与虚拟机栈一样,本地方法栈区域也会抛出StackOverflowError和OutOfMemoryError异常
所有线程共享的内存
堆
- Java堆是被所有线程共享的一块内存区域
- 唯一目的就是存放对象实例和数组
- Java堆是垃圾收集器管理的主要区域
- 现在收集器基本都是采用的分代收集算法
- Java堆中还可以细分为:新生代和老年代
- 如果在堆中没有内存完成实例分配,并且堆也无法再扩展时,将会抛出OutOfMemoryError异常
方法区(Method Area)
- 各个线程共享的内存区域,它用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。
- 对于HotSpot虚拟机,很多人愿意把方法区称为”永久代”Permanent Generation),本质上两者并不等价,仅仅是因为HotSpot虚拟机的设计团队选择把GC分代收集扩展至方法区,或者说使用永久代来实现方法区而已。
- 垃圾收集行为在这个区域是比较少出现的
- 这个区域的内存回收目标主要是针对常量池的回收和对类型的卸载
- 当方法区无法满足内存分配需求时,将抛出OutOfMemoryError异常
运行时常量池(Runtime Constant Pool)是方法区的一部分
- 编译期生成的各种字面量和符号引用,类加载后存放到方法区的运行时常量池中
- 运行期间也可能将新的常量放入池中,这种特性被开发人员利用得比较多的便是String类的intern()方法