java 中虚地址的应用,JAVA经常使用知识总结(三)——JAVA虚拟机

先附一张JAVA虚拟机内存结构图:html

c732c5594c49197654ea4978aec3630b.png

其中JAVA虚拟机的线程问题java

堆空间的内存分配

JVM class类加载机制

JVM中的GC垃圾回收机制

三个大方面进行详细说明面试

堆空间的内存分配

05059033af3468f452a5fdeca38c7440.png

HotSpot JVM①把年轻代分为了三部分:1个Eden区和2个Survivor区(分别叫from和to)。默认比例为8:1,为啥默认会是这个比例,接下来咱们会聊到。通常状况下,新建立的对象都会被分配到Eden区(一些大对象特殊处理),这些对象通过第一次Minor GC后,若是仍然存活,将会被移到Survivor区。对象在Survivor区中每熬过一次Minor GC,年龄就会增长1岁,当它的年龄增长到必定程度时,就会被移动到年老代中。算法

由于年轻代中的对象基本都是朝生夕死的(80%以上),因此在年轻代的垃圾回收算法使用的是复制算法,复制算法的基本思想就是将内存分为两块,每次只用其中一块,当这一块内存用完,就将还活着的对象复制到另一块上面。复制算法不会产生内存碎片。在GC开始的时候,对象只会存在于Eden区和名为“From”的Survivor区,Survivor区“To”是空的。紧接着进行GC,Eden区中全部存活的对象都会被复制到“To”,而在“From”区中,仍存活的对象会根据他们的年龄值来决定去向。年龄达到必定值(年龄阈值,能够经过-XX:MaxTenuringThreshold来设置)的对象会被移动到年老代中,没有达到阈值的对象会被复制到“To”区域。通过此次GC后,Eden区和From区已经被清空。这个时候,“From”和“To”会交换他们的角色,也就是新的“To”就是上次GC前的“From”,新的“From”就是上次GC前的“To”。无论怎样,都会保证名为To的Survivor区域是空的。Minor GC会一直重复这样的过程,直到“To”区被填满,“To”区被填满以后,会将全部对象移动到年老代中。post

附两个分别为堆溢出和栈溢出的代码:性能

堆溢出:spa

1 public static voidmain(String[] args) {2 List list = new ArrayList();3 int i=0;4 while(true){5 list.add(new byte[5*1024*1024]);6 System.out.println("分配次数:"+(++i));7 }8 }

分配次数:1

分配次数:2

分配次数:3

分配次数:4

分配次数:5

分配次数:6

分配次数:7

分配次数:8

分配次数:9

分配次数:10

分配次数:11

Exception in thread "main" java.lang.OutOfMemoryError: Java heap space

at com.cnten.bdcloud.device.controller.StackSOFTest.main(StackSOFTest.java:29)线程

栈溢出:3d

1 public classStackSOFTest {2

3 int depth = 0;4

5 public voidsofMethod(){6 depth ++;7 sofMethod();8 }9

10 public static voidmain(String[] args) {11 StackSOFTest test = null;12 try{13 test = newStackSOFTest();14 test.sofMethod();15 } finally{16 System.out.println("递归次数:"+test.depth);17 }18 }19

20 }

递归次数:6868

Exception in thread "main" java.lang.StackOverflowError

at com.cnten.bdcloud.device.controller.StackSOFTest.sofMethod(StackSOFTest.java:11)

at com.cnten.bdcloud.device.controller.StackSOFTest.sofMethod(StackSOFTest.java:12)

at com.cnten.bdcloud.device.controller.StackSOFTest.sofMethod(StackSOFTest.java:12)

at com.cnten.bdcloud.device.controller.StackSOFTest.sofMethod(StackSOFTest.java:12)...code

永久代

指内存的永久保存区域,主要存放Class和Meta(元数据)的信息,Class在被加载的时候被放入永久区域. 它和和存放实例的区域不一样,GC不会在主程序运行期对永久区域进行清理。因此这也致使了永久代的区域会随着加载的Class的增多而胀满,最终抛出OOM异常。

在Java8中,永久代已经被移除,被一个称为“元数据区”(元空间)的区域所取代。

元空间的本质和永久代相似,都是对JVM规范中方法区的实现。不过元空间与永久代之间最大的区别在于:元空间并不在虚拟机中,而是使用本地内存。所以,默认状况下,元空间的大小仅受本地内存限制。类的元数据放入 native memory, 字符串池和类的静态变量放入java堆中. 这样能够加载多少类的元数据就再也不由MaxPermSize控制, 而由系统的实际可用空间来控制.

采用元空间而不用永久代的几点缘由:(参考:http://www.cnblogs.com/paddix/p/5309550.html)

一、为了解决永久代的OOM问题,元数据和class对象存在永久代中,容易出现性能问题和内存溢出。

二、类及方法的信息等比较难肯定其大小,所以对于永久代的大小指定比较困难,过小容易出现永久代溢出,太大则容易致使老年代溢出(由于堆空间有限,此消彼长)。

三、永久代会为 GC 带来没必要要的复杂度,而且回收效率偏低。

四、Oracle 可能会将HotSpot 与 JRockit 合二为一。

JVM class类加载机制

JVM类加载机制主要采用的是双亲委派模型

caa569923766701f33b1ce8385bd2abd.png

双亲委派的工做过程:

若是一个类加载器收到了类加载的请求,它首先不会本身去尝试加载这个类,而是把这个请求委派给父类加载器去完成,每个层次的类加载器都是如此,所以全部的加载请求最终都应该传送到顶层的启动类加载器中,只有父类加载器反馈本身没法完成这个加载请求时,子加载器才会尝试本身去加载。

双亲委派机制的最大优势就是使得java类随着它的类加载器一块儿具有了一种带有优先级的层次关系。尤为是保证了基础类的统一性,保证了java程序的稳定运行。

用双亲委派模型的好处在于Java类随着它的类加载器一块儿具有了一种带有优先级的层次关系。例如类java.lang.Object,它存在在rt.jar中,不管哪个类加载器要加载这个类,最终都是委派给处于模型最顶端的Bootstrap ClassLoader进行加载,所以Object类在程序的各类类加载器环境中都是同一个类。相反,若是没有双亲委派模型而是由各个类加载器自行加载的话,若是用户编写了一个java.lang.Object的同名类并放在ClassPath中,那系统中将会出现多个不一样的Object类,程序将混乱。所以,若是开发者尝试编写一个与rt.jar类库中重名的Java类,能够正常编译,可是永远没法被加载运行。

JVM中的GC垃圾回收机制

b30e91cdeb5e5a6da2f1fc5b04745a15.png

将对象按其生命周期的不一样划分红:年轻代(Young Generation)、年老代(Old Generation)、持久代(Permanent Generation)。其中持久代主要存放的是类信息,因此与java对象的回收关系不大,与回收息息相关的是年轻代和年老代

年轻代:是全部新对象产生的地方。年轻代被分为3个部分——Enden区和两个Survivor区(From和to)

由于大多数新生代对象都不会熬过第一次 GC。因此不必 1 : 1 划分空间。能够分一块较大的 Eden 空间和两块较小的 Survivor 空间,每次使用 Eden 空间和其中一块 Survivor。当回收时,将 Eden 和 Survivor 中还存活的对象一次性复制到另外一块 Survivor 上,最后清理 Eden 和 Survivor 空间。大小比例通常是 8 : 1 : 1,每次浪费 10% 的 Survivor 空间。可是这里有一个问题就是若是存活的大于 10% 怎么办?这里采用一种分配担保策略:多出来的对象直接进入老年代。

当Eden区被对象填满时,就会执行Minor GC。并把全部存活下来的对象转移到其中一个survivor区(假设为from区)。Minor GC一样会检查存活下来的对象,并把它们转移到另外一个survivor区(假设为to区)。这样在一段时间内,总会有一个空的survivor区。通过屡次GC周期后,仍然存活下来的对象会被转移到年老代内存空间。一般这是在年轻代有资格提高到年老代前经过设定年龄阈值来完成的。须要注意,Survivor的两个区是对称的,没前后关系,from和to是相对的。

年老代:在年轻代中经历了N次回收后仍然没有被清除的对象,就会被放到年老代中,能够说他们都是久经沙场而不亡的一代,都是生命周期较长的对象。对于年老代和永久代,就不能再采用像年轻代中那样搬移腾挪的回收算法,由于那些对于这些回收战场上的老兵来讲是小儿科。一般会在老年代内存被占满时将会触发Full GC,回收整个堆内存。 新生代和老年代为 1:2

年轻代:涉及了复制算法(把空间分红两块,每次只对其中一块进行 GC。当这块内存使用完时,就将还存活的对象复制到另外一块上面);年老代:涉及了“标记-整理(Mark-Sweep)”的算法(直接标记清除就可)

内存回收以前要作的事情就是判断哪些对象不可用

引用计数法 (难以解决循环引用问题)

可达性分析法(经过一系列的 ‘GC Roots’ 的对象做为起始点,从这些节点出发所走过的路径称为引用链。当一个对象到 GC Roots 没有任何引用链相连的时候说明对象不可用)

3232ca36fcb08a368cb2c64208b4aeda.png

(即便在可达性分析算法中不可达的对象,也并不是是“facebook”的,这时候它们暂时出于“缓刑”阶段,一个对象的真正死亡至少要经历两次标记过程:若是对象在进行中可达性分析后发现没有与 GC Roots 相链接的引用链,那他将会被第一次标记而且进行一次筛选,筛选条件是此对象是否有必要执行 finalize() 方法。当对象没有覆盖 finalize() 方法,或者 finalize() 方法已经被虚拟机调用过,虚拟机将这两种状况都视为“没有必要执行”。若是这个对象被断定为有必要执行 finalize() 方法,那么这个对象竟会放置在一个叫作 F-Queue 的队列中,并在稍后由一个由虚拟机自动创建的、低优先级的 Finalizer 线程去执行它。这里所谓的“执行”是指虚拟机会出发这个方法,并不承诺或等待他运行结束。finalize() 方法是对象逃脱死亡命运的最后一次机会,稍后 GC 将对 F-Queue 中的对象进行第二次小规模的标记,若是对象要在 finalize() 中成功拯救本身 —— 只要从新与引用链上的任何一个对象简历关联便可。finalize() 方法只会被系统自动调用一次。)

下面四种引用强度一次逐渐减弱

强引用

相似于 Object obj = new Object(); 建立的,只要强引用在就不回收。

软引用

SoftReference 类实现软引用。在系统要发生内存溢出异常以前,将会把这些对象列进回收范围之中进行二次回收。

弱引用

WeakReference 类实现弱引用。对象只能生存到下一次垃圾收集以前。在垃圾收集器工做时,不管内存是否足够都会回收掉只被弱引用关联的对象。

虚引用

PhantomReference 类实现虚引用。没法经过虚引用获取一个对象的实例,为一个对象设置虚引用关联的惟一目的就是能在这个对象被收集器回收时收到一个系统通知。

注释一:HotSpot历史

SUN的JDK版本从1.3.1开始运用HotSpot虚拟机, 2006年末开源,主要使用C++实现,JNI接口部分用C实现。

HotSpot是较新的Java虚拟机,用来代替JIT(Just in Time),能够大大提升Java运行的性能。

Java原先是把源代码编译为字节码在虚拟机执行,这样执行速度较慢。而HotSpot将经常使用的部分代码编译为本地(原生,native)代码,这样显着提升了性能。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值