深入理解JVM笔记之入门

Java虚拟机结构:

 

运行时区域

虚拟机栈:每一个线程都有一个虚拟机栈,线程中每调用一个方法都会开启一个栈帧,栈帧里面保存方法中的局部变量。所以,不同栈帧里面即使变量名相同也不会有冲突的。栈帧:每一个线程每一个方法都会开启一个栈帧。

堆:存放对象实例,最大一块内存。

方法区:也叫永久区。class文件,常量,静态变量。

程序计数器:每一个线程都有一个独立的程序计数器。记录的是当前线程所执行的字节码指令的地址。

堆内存

堆内存结构

https://i-blog.csdnimg.cn/blog_migrate/05438a43e8932709625a949a208766d2.png

分为新生代,老年代。新生代又分为eden和两个survivor。

对象分配的方式:

new 一个对象,如果该对象很大,就直接分配到老年区,如果不是很大就分配带新生代的eden区域。

第一次GC的时候,会把eden区域没有被回收的对象(有引用)拷贝到s0区域。

第二次GC的时候会把eden区域没有被回收的和s0区域中的对象拷贝

到s1区域,并且清除s0区域。

再次GC的时候,会把eden区域没有被内存回收的对象和s1区域的对象拷贝到s0区域,然后清楚s1区域,一直这样s0区域和s1区域交替使用。

如果一个对象在GC的过程中,经过很多次都没有被GC,最终会被移动到老年区这个次数可以通过参数来进行配置

常配置eden:s0:s1区域的内存比例是8:1:1,new新生代区域:old区域的内存比例是1:3。

一般来说,假如eden有100个对象,经过一次GC之后,可能会有90个都会被回收,然后再复制到s0中去。

GC原理

什么是垃圾?

所有指向对象的引用都已失效,不可能再有程序能调用到这个对象,那么这个对象就成了垃圾,应该被回收。

如何确定垃圾

引用计数:每当多一个引用指向对象时,引用计数加一,每当少一个引用指向对象时,引用计数减一,引用计数减到零,对象就可以被回收了。不可行,会有循环引用的问题。

正向可达:于GC Roots的可达性分析。

什么是GC Roots?所有类的静态变量,每个线程调用栈上的本地变量。

所有这些对象,以及被这些对象所指向的对象,都是活的对象。活的对象所指向的对象也是活的对象。

所以只要在GC的时刻,让程序暂停运行,然后从GC Roots开始分析,最后没有被标记为活对象的对象就是垃圾了

循环引用的问题:

public class Main {

    public static void main(String[] args) {

        MyObject object1 = new MyObject();

        MyObject object2 = new MyObject();

         

        object1.object = object2;

        object2.object = object1;

         

        object1 = null;

        object2 = null;

    }

}

若是使用引用计数法:

最后面两句将object1和object2赋值为null,也就是说object1和object2指向的对象已经不可能再被访问,但是由于它们互相引用对方,导致它们的引用计数器都不为0,那么垃收集器就永远不会回收它们

可达性分析算法

这个算法的基本思路就是通过一系列的称为“GC Roots”的对象作为起始点,从这些节点开始向下搜索,搜索所走过的路径称为引用链(Reference Chain),当一个对象到GC Roots没有任何引用链相连(用图论的话来说,就是从GC Roots到这个对象不可达)时,则证明此对象是不可用的。

对象的强,弱,软,虚引用

http://blog.51cto.com/attachment/200712/200712011196500925619.jpg

强引用是使用最普遍的引用。如果一个对象具有强引用,那垃圾回收器绝不会回收它。当内存空间不足,Java虚拟机宁愿抛出OutOfMemoryError错误,使程序异常终止,也不会靠随意回收具有强引用的对象来解决内存不足的问题。

如果一个对象只具有软引用,则内存空间足够,垃圾回收器就不会回收它;如果内存空间不足了,就会回收这些对象的内存。只要垃圾回收器没有回收它,该对象就可以被程序使用。

弱引用与软引用的区别在于:只具有弱引用的对象拥有更短暂的生命周期。在垃圾回收器线程扫描它所管辖的内存区域的过程中,一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存。不过,由于垃圾回收器是一个优先级很低的线程,因此不一定会很快发现那些只具有弱引用的对象

虚引用顾名思义,就是形同虚设,与其他几种引用都不同,虚引用并不会决定对象的生命周期。如果一个对象仅持有虚引用,那么它就和没有任何引用一样,在任何时候都可能被垃圾回收器回收。

虚引用主要用来跟踪对象被垃圾回收器回收的活动。虚引用与软引用和弱引用的一个区别在于:虚引用必须和引用队列 ReferenceQueue)联合使用。当垃圾回收器准备回收一个对象时,如果发现它还有虚引用,就会在回收对象的内存之前,把这个虚引用加入到与之 关联的引用队列中。

 

垃圾收集算法

Mark-sweep标记清除

https://i-blog.csdnimg.cn/blog_migrate/0fbad9c79dfe337e5d86da6e55fcd6d9.png

经过正向可达性分析后,将垃圾做标记,然后将做了标记的都给清楚。缺点是导致内存碎片化。

Copying 复制压缩

https://i-blog.csdnimg.cn/blog_migrate/482ac55b6b90f910aa5b7e18a03dcd19.png

将内存一般分为A区域,一半分为B区域。图中我们将前两行分为A,后两行分为B。刚开始的时候,我们只使用A区域的内存,而不使用B区域的内存。

第一次GC,经过一次可达性分析后,我们将A中存活对象直接复制到B区域,然后直接将整块A区域清除。A区域变成未使用的。

第二次GC,同理,将B的存活对象复制到A,将B清除,B变为空。

这样A和B区域交互使用。

这个算法可以解决内存碎片化的问题,但是会导致内存浪费,一次只能使用一般的内存。

在新生代就是使用这种。survior之间复制。

Mark-Compact

https://i-blog.csdnimg.cn/blog_migrate/942199b33a17e1d1ae8d1adadb3858e1.png

 

在老年代中使用,因为老年代中可回收的垃圾比较少,所以也能保证效率,但是效率比复制压缩算法较低,但是节约内存资源。

总结

在新生代中,因为垃圾会比较多,存活的对象少,占用内存小,所以采用copying。

在老年代中,垃圾比较少,内存占用多,使用mark-compact

当new出来一个对象的时候,若这个对象比较大,直接将其放入老年代,若比较小,将其放入eden。

第一次GC:假设此时eden中有一百个对象,按照正向可达性分析后,发现有90个垃圾,则采取copying方法,将剩下存活着的10个对象全部复制到s0中去,清空eden。

第二次GC:假设此时eden中有一百个对象,按照正向可达性分析后,发现有90个垃圾,则采取copying方法,将剩下存活着的10个对象全部复制到s1中去,并且将s0中的存活对象也全部复制到s1中去。

如此往复,s0,s1交互使用。。。。。

新生代采用copying算法原因:一般来说,eden中垃圾会很多,假如eden100个对象一次GC大约有90个都是垃圾,所以每次只需要复制很少的几个对象就可以了,这样解决了内存碎片化问题而且复制效率本身就很高,对于浪费内存的缺点,survior本身相对于eden,老年代占比就很少(一般新生代:老年代=13edensurvior=811),所以浪费内存就很少了。

而对于老年代的垃圾:

老年代本身占据内存很大,我们要是再去使用copying,就会浪费大量内存。老年代内对象就是多次GC之后存活下来的对象,所以一次GC老年代的垃圾会很少,会有大量存活,所以采取mark-compact,将存货对象转移到垃圾对象释放的空间里(垃圾对象很少,这种转移也很少)。

https://i-blog.csdnimg.cn/blog_migrate/51536cb97e15eb9b664b38d4c5e36d35.png

JVM参数

  • 标准参数

-X  非标准参数

-XX  不稳定参数

垃圾收集器

Serial Collector

XX + UseSerialGC 序列化垃圾收集器,一个单线程的收集器,实际中使用的并不多。

Parallel Collector:

多线程收集,并发量大,但是在每次垃圾收集的时候,会冻结别的线程的执行,回导致JVM停顿。

CMS

并发收集,分区处理。停顿时间短,在垃圾收集的时候,JVM还可以运行。

G1

不仅停顿时间短(这是一个平衡点)而且并发量大。介于CMS和parallel之间。

根据需求来决定垃圾收集器。

java对象分配

栈上分配

当new出一个小的对象来的时候,并且开了JVM栈上分配优化的话,那么会优先分配到栈(线程栈)上面去。JavaServer模式默认会开启栈优化。

优点:方法结束后,栈帧销毁,栈上的对象也就直接销毁了,无需垃圾回收,效率特别高。

无逃逸:如果方法外面有一个引用指向这个对象,称做逃逸。

标量替换:

线程本地分配(voliatile

每一个线程都有自己的Thread local Allocation Buffer。

占用eden。默认1%。

如果每一个线程都要放入eden的同一块区域那么这个区域就要进行加锁,但是每个线程的数据都有自己的一块独立的区域那么就不需要加锁了,不加锁就提高了访问效率。?

老年代

大对象上述两种都分配不了那么就先看看自己是否是一个大对象,如果是就分配到老年代。

eden

如果自己是一个不太大的对象就分配到eden区来。

 

 

 

 

 

 

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值