JVM

jvm是Java虚拟机,Java程序靠jvm在各个系统上运行。jvm运行时数据区有五个区域,栈,本地方法栈,程序计数器,堆,方法区。栈,本地方法栈,程序计数器是线程私有的,堆和方法区是线程共有的。

栈:存储当前线程运行方法时所需的数据,指令,返回地址。我们知道方法运行时都会出现一个栈帧,用于存储局部变量表,操作数栈,动态链接,方法出口等。

局部变量表存放了编译期可知的各种基本数据类型,对象引用(仅限局部变量的,不包含成员变量的)。

栈中存的是基本数据类型和堆中对象的引用。

本地方法栈:和栈类似,存放本地方法中局部变量和对象的引用。

程序计数器:保存当前线程所执行的字节码位置。是内存中划分出的一块小内存区域,记录了我们下一条要运行指令的地址。之后,执行引擎会从程序计数器中,获得指令的地址,拿到操作指令,对其进行执行。栈有程序计数器,本地方法栈程序计数器为空。


堆:存放对象。堆有新生代(占堆内存的1/3),老年代(占堆内存的2/3)。新生代分为eden区和两个servivor区,比例是8:1:1。新生成的对象大部分保存在eden区,需要连续存储的大对象保存在老年代。设置两个Survivor区最大的好处就是解决了碎片化。

1、分代回收

        刚刚新建的对象在Eden中,一旦Eden满了,触发一次Minor GC,存活的对象放到servivor from 区,Eden被清空;再次触发Minor GC时,Eden和 servivor from 区中的存活对象会被复制送入survivor to区里。(这个过程非常重要,因为这种复制算法保证了 to区中来自 from区 和Eden两部分的存活对象占用连续的内存空间,避免了碎片化的发生)。from区和Eden被清空,然后交换 from 与 to ,原来的from 区变成 to 区,新 to 区里面是空的啥也没有;原来的 to 区变成 from 区,新 from 里面是两次Minor GC后仍存活的对象。如此循环往复。对象每熬过一次Minor GC,年龄就会增加1岁,默认15岁时(年龄阈值,可以通过-XX:MaxTenuringThreshold来设置),该对象就会被送到老年代中。上述机制最大的好处就是,整个过程中,永远有一个survivor space是空的,另一个非空的survivor space无碎片。(Eden 区->Survivor 区后对象的初始年龄变为 1)

2、 算法

        新生代采用停止复制清理法,老年代存储的对象比年轻代多得多,而且不乏大对象,对老年代进行内存清理时,如果使用停止-复制算法,则相当低效。一般,老年代用的算法是标记-压缩算法,即:标记出仍然存活的对象(存在引用的),将所有存活的对象向一端移动,以保证内存的连续。

3、空间分配担保

3.1 谁进行空间担保?

        JVM使用分代收集算法,将堆内存划分为年轻代和老年代,两块内存分别采用不同的垃圾回收算法,空间担保指的是老年代进行空间分配担保。

3.2 什么是空间分配担保?

        在发生Minor GC之前,虚拟机会检查老年代最大可用的连续空间是否大于新生代所有对象的总空间,

  如果大于,则此次Minor GC是安全的

  如果小于,则虚拟机会查看HandlePromotionFailure设置值是否允许担保失败。如果HandlePromotionFailure=true,那么会继续检查老年代最大可用连续空间是否大于历次晋升到老年代的对象的平均大小,如果大于,则尝试进行一次Minor GC,但这次Minor GC依然是有风险的;如果小于或者HandlePromotionFailure=false,则改为进行一次Full GC。

3.3 为什么要进行空间担保?

        因为新生代采用复制收集算法,假如大量对象在Minor GC后仍然存活(最极端情况为内存回收后新生代中所有对象均存活),而Survivor空间是比较小的,这时就需要老年代进行分配担保,把Survivor无法容纳的对象放到老年代。老年代要进行空间分配担保,前提是老年代得有足够空间来容纳这些对象,但一共有多少对象在内存回收后存活下来是不可预知的,因此只好取之前每次垃圾回收后晋升到老年代的对象大小的平均值作为参考。使用这个平均值与老年代剩余空间进行比较,来决定是否进行Full GC来让老年代腾出更多空间。

注意:JDK 6Update 24之后,只要老年代的连续空间大于新生代对象总大小或者历次晋升的平均大小就会进行minorGC,否则进行Full GC.

4、GC触发条件:

        Minor GC触发条件:Eden区满时

        Full GC触发条件:

        (1)调用System.gc时,系统建议执行Full GC,但是不必然执行

        (2)老年代空间不足

        (3)方法区空间不足

        (4)通过Minor GC后进入老年代的平均大小大于老年代的可用内存

        (5)由Eden区、From Space区向To Space区复制时,对象大小大于To Space可用内存,则把该对象转存到老年代,且老年代的可用内存小于该对象大小。

5、对象进入老年代的四种情况:

  1. 假如进行Minor GC时发现,存活的对象在ToSpace区中存不下,那么把存活的对象存入老年代
  2. 大对象直接进入老年代。假设新创建的对象很大,比如为5M(这个值可以通过PretenureSizeThreshold这个参数进行设置,默认3M),那么即使Eden区有足够的空间来存放,也不会存放在Eden区,而是直接存入老年代。
  3. 长期存活的对象将进入老年代。对象在新生代的年龄增加到一定程度(默认15岁,配置参数-XX:MaxTenuringThreshold),就会被晋升到老年代中
  4. 动态对象年龄判定。还有一种情况,如果在From空间中,相同年龄所有对象的大小总和大于Survivor空间的一半,那么年龄大于等于该年龄的对象就会被移动到老年代,而不用等到15岁(默认)。

方法区:非堆区,永久代。存放已被虚拟机加载的:类信息、常量、静态变量、即时编译器编译后的代码等数据。java8中,已经移除了永久代,新加了一个叫做元数据区的native内存区。元空间与永久代之间最大的区别在于:元空间并不在虚拟机中而是使用本地内存。因此,默认情况下,元空间的大小仅受本地内存限制。类的元数据当如native memeory,字符串池和类的静态变量放入java堆中,这样可以加载多少类的元数据就不在由MaxPermSize控制,而由系统的实际可用空间来控制。
 

GC垃圾回收:回收堆中不用的对象,采用可达性分析法,找到一系列对象作为GC ROOT,不能被GC ROOT引用到的对象就回收。GC ROOT:虚拟机(JVM)栈中引用对象;方法区中的类静态属性引用对象;方法区中常量引用的对象(final 的常量值);本地方法栈JNI的引用对象 。算法有引用计数法,标记清除算法,复制法。引用计数法是对象被引用一次,记为+1,没有被引用?记为-1,为0时证明不被引用。但是问题是不能解决循环引用的问题?。标记清除算法是划分区域,清除不被引用的部分,缺点是会产生内存碎片。复制法就是两个区域,from和to,from块将存活的对象复制到to块,清除from块。

java的类加载器以及如何自定义类加载器

ClassLoader作用

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值