java内存区域:
java虚拟机所管理的内存将会包括以下几个运行时数据区域:
1、程序计数器:可以看作当前线程所执行的字节码的行号指示器,字节码编译器工作时就是通过改变这个计数器的值来选取下一条要执行的字节码指令;
2、虚拟机栈:描述的是java方法执行的内存模型:每个方法在执行的同时都会创建一个栈帧用于存储局部变量表、操作数栈、动态链接、方法出口等信息。每一个方法从调用直至执行完成的过程,就对应着一个栈帧在虚拟机栈中从入栈到出栈的过程;
3、本地方法栈:与虚拟机栈作用相似,区别在于虚拟机栈为虚拟机执行java方法服务,而本地方法栈则是为虚拟机用到的native方法服务;
4、java堆:是java虚拟机所管理的内存中最大的一块,被所有线程共享,在虚拟机启动的时候创建。其作用是存放对象实例,几乎所用的对象实例都在这里分配内存;
5、方法区:用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。
对象的创建过程:
1、虚拟机遇到一条new指令时,首先会去检查这个指令的参数是否能在常量池中定位到一个类的符号引用,并检查这个符号引用代表的类是否已被加载、解析和初始化过。如果么有,那必须先执行相应的类加载过程;
2、在类加载检查通过后,接下来虚拟机将为新生对象分配内存,对象所需要的内存大小在类加载完成之后可以完全确定,为对象分配空间的任务等同于把一块确定大小的内存从java堆中划分出来,根据java堆是否规整采用“指针碰撞”或“空闲列表”的分配方式;
3、除划分内存空间之外,还要考虑的问题是对象创建在虚拟机中是否是非常频繁的行为从而带来同步的问题。一种解决方案是对分配内存空间的动作进行同步处理,另一种解决方案是把内存分配的动作按照线程划分在不同的空间之中进行,即每个线程在java堆中预先分配一块小内存,称为本地线程分配缓冲(TLAB),哪个线程需要分配内存,就在哪个线程的TLAB上分配,只有TLAB用完并分配新的TLAB时,才需要同步锁定;
4、内存分配完之后,虚拟机需要将分配到的内存空间都初始化为零(不包括对象头);
5、接下来虚拟机要对对象进行必要的设置,例如这个对象是哪个类的实例、如何才能找到类的元数据信息、对象的哈希码、对象的GC分代年龄等信息,这些信息存放在对象的对象头之中;
垃圾收集器与内存分配策略
1、程序计数器、虚拟机栈、本地方法栈:随线程而生,随线程而灭,其内存大小大题上可以认为是编译器可知的;方法区、java堆:各线程共享,只有在程序运行时才能知道会创建哪些对象,这部分内存的分配和回收是动态的;
2、引用计数算法:给对象中添加一个引用计数器,每当一个地方引用它时,计数器值就加1,当引用失效时,计数器值就减1;但不能解决对象间相互循环引用的问题。
3、可达性分析算法:通过一系列的称为“GC Roots”的对象作为起始点,从这些节点开始向下搜索,搜索所走过的路径称为引用链,当一个对象到GC Roots没有任何引用链相连时,则证明此对象是不可用的。
4、java中可作为GC Roots的对象包括:虚拟机栈中引用的对象、方法区中静态属性引用的对象、方法区中常量引用的对象、本地方法栈中JNI引用的对象。
5、垃圾收集算法:
标记-清除算法:缺点:效率问题(标记和清除两个过程的效率都不高)和空间问题(标记清除后会产生大量不连续的内存碎片)。
复制算法:将可用内存按容量划分为大小相等的两块,每次只使用其中的一块。当一块的内存用完了就将还存活的对象复制到另外一块上面,缺点:将内存缩小为原来的一半。(现在的商业虚拟机都采用这种收集算法来回收新生代:将内存分为一块较大的Eden空间和两块较小的Survivor空间,每次使用Eden和其中一块Survivor,每当new一个对象时会将其保存在Eden中,当回收时,将Eden和Survivor中还存活的对象一次性复制到另一块Survivor上,最后清理掉Eden和刚才使用过的Survivor空间,当Survivor空间不够用时,需要依赖其它内存进行分配担保)
标记-整理算法:与标记-清除算法一样,但后续步骤不是直接对可回收对象进行清理,而是让所有存活的对象都向一端移动,然后直接清理掉端边界以外的内存。
分代收集算法:根据对象存活周期的不同将内存划分为几块。一般是把java堆分为新生代和老年代,新生代使用复制算法进行垃圾回收,老年代使用标记-清除或标记-整理算法来进行垃圾回收。
6、垃圾收集器:
CMS收集器,是一种以收货最短回收停顿时间为目标的收集器,目前很大一部分的java应用集中在互联网站或者B/S系统的服务端上,这类应用尤其重视服务的相应速度,希望系统停顿时间最短,以给用户带来较好的体验。CMS收集器是基于“标记-清除”算法实现的,优点是并发收集和低停顿。缺点:CMS收集器对CPU资源非常敏感(占用CPU资源而导致应用程序变慢,总吞吐量降低)、无法处理浮动垃圾、收集结束会产生大量空间碎片。
G1收集器,与其他GC收集器相比G1具备如下特点:
并行与并发:G1能充分利用CPU、多核环境下的硬件优势,使用多个CPU来缩短Stop-The-World停顿的时间;
分代收集:采用不同的方式去处理新创建的对象和已经存活了一段时间、经历多次GC的旧对象以获取更好的收集效果;
空间整合:相对于CMS的一大优势,从整体来看是基于“标记-整理”算法实现的收集器,从局部来看是基于“复制”算法实现的。总之G1运作期间不会产生内存空间碎片,收集后能提供规整的可用内存。有利于程序长时间运行,分配大型对象时不会因为无法找到连续内存空间而提前触发下一次GC。
可预测的停顿:是相对于CMS的另一大优势,除同样追求低停顿外,G1收集器还能建立可预测的停顿时间模型,能让使用者明确指定在一个长度为M毫秒的时间片段内,消耗在垃圾收集上的时间不得超过N毫秒。
java内存模型:
在Java虚拟机规范中试图定义一种Java内存模型(Java Memory Model,JMM)来屏蔽各个硬件平台和操作系统的内存访问差异,以实现让Java程序在各种平台下都能达到一致的内存访问效果。java内存模型定义了程序中变量的访问规则,规定所有的变量都是存在主存当中,每个线程都有自己的工作内存。线程对变量的所有操作都必须在工作内存中进行,而不能直接对主存进行操作。并且每个线程不能访问其他线程的工作内存。
java语言本身对原子性、可见性以及有序性提供的保证:
1、原子性:jvm对基本数据类型的读取和赋值操作是原子性操作,即这些操作是不可被中断的,要么执行,要么不执行。java提供的synchronized和lock可以保证任意时刻只有一个线程执行同步代码,从而保证了原子性。
2、可见性:对于可见性,java提供了volatile关键字。当一个共享变量被volatile修饰时,它会保证修改的值会立刻被更新到主存,当有其他线程需要读取时,它会去内存中读取新值。另外synchronized和lock也能保证可见性,synchronized和lock能保证同一时刻只有一个线程获取锁然后执行同步代码,并且在释放锁之前会将对变量的修改刷新到主存当中,因此也可以保证可见性。
3、有序性:可以通过volatile关键字来保证一定的“有序性”。而synchronize和lock保证每个时刻只有一个线程执行同步代码,自然保证了有序性。
(volatile的应用:状态标记量、双重检查)
参考资料:
《深入理解java虚拟机》
https://www.cnblogs.com/dolphin0520/p/3920373.html
网友评论