Ⅰ jvm堆内存和非堆内存(小白入门文,各博客视频基础总结)
一:堆内存和非堆内存定义
java虚拟机具有一个堆(Heap),堆是运行时数据区域,所有类实例和数组的内存均从此处分配。堆是Java虚拟机启动时创建的。在JVM中堆之外的内u你成为非堆内存(Non-heap memory)。
堆内存以及相应垃圾回收算法
1.堆的大小可以固定,也可以扩大和缩小,堆内存不需要是连续空间。
2.对象创建后进入Eden。年轻代分为Eden和Survivor。Survivor由FromSpace和ToSpace组成。Eden区占大容量,Survivor占小容量,默认比例8:1:1。
MinorGC:采用复制算法。首先把Eden和ServivorFrom区域中存活的对象赋值到ServivorTo区域(如果对象年龄达到老年标准/ServivorTo位置不够了,则复制到老年代),同时对象年龄+1,然后清空Eden和ServivorFrom中的对象。然后ServivorTo和ServivorFrom互换。
3.老年代
老年代存放生命周期长的内存对象。
老年代对象相对稳定,所以不会频繁GC。在进行MajorGC前一般都先进行一次MinorGC,使新生代的对象进入老年代,导致空间不够用时才触发。当无法找到足够大的连续空间分配给新晋的对象也会提前触发MajorGC进行垃圾回收。
MajorGC:如果使用CMS收集器,采用标记-清除算法。首先扫描老年代,标记游笑孝所有可回收对象,标记完成后统一回收所有被标记对象。同时会产生不连续的内存碎片。碎片过多会导致以后程序运行需要分配较大对象时,无法找到足够的连续内存,而不得已再次出发GC。否则采用标记-压缩算法。
标记-压缩:在标记可回收对象后,将不可回收对象移向一端,然后清除标记对象。
当神稿老年代也满了装不下时,抛出OOM异常。
二:永久代
内存中永久保存的区域,主要存放Class和Meta(元数据)的信息,Class在被加载的时候被放入永久区域。他和存放实例的区域不同,GC不会再主程序运行期对永久区进行清理。所以也可可能导致永久代区域随着加载Class的增多而胀满,抛出OOM。
Java8中,永久代已经被移除,被一个成为“元数据区”(元空间)的区域所取代。
元空间的本质与永久代类似,都是JVM方法区的实现。不过元空间使用本地内存,永久代在JVM虚拟机中。因此,默认情况下,元空间的大小受本地内存限制。类的元数据放入native memory,字符串常量池和类的静态变量放入java堆中,这样可以加载多少类的元数据就不再由MaxPermSize控制,而是由系统实际可用空间控制。
1元空间解决了永久代的OOM问题,元数据和class对象在永久代容易出现性能问题和内存溢出。
2类的方法信息等比较难确定其大小,对于永久代的大小指定比较困难,小永久代溢出,大老年代溢出。
3永久代会为GC带来不必要的复杂度,回收效率低。
三:堆内存参数调优
1.-Xms 设置初始分配内存大小,默认物理内存1/64
2.-Xmx 设置最大分升碰配内存,默认物理内存1/4
long maxMemory = Runtime.getRuntime().maxMemory(); long totalMemory = Runtime.getRuntime().totalMemory(); System.out.println("最大分配内存"+maxMemory/(double)1024/1024+"MB "+maxMemory/(double)1024/1024/1024+"GB"); System.out.println("默认分配内存"+totalMemory/(double)1024/1024+"MB "+totalMemory/(double)1024/1024/1024+"GB");
Ⅱ Java垃圾回收:GC在什么时候对什么做了什么
GC在什么时候对什么做了什么?
要回答这个问题,先了解下GC的发展史、jvm运行时数据区的划分、jvm内存分配策略、jvm垃圾收集算法等知识。
先说下jvm运行时数据的划分,粗暴的分可以分为堆区(Heap)和栈区(Stack),但jvm的分法实际上比这复杂得多,大概分为下面几块:
1、程序计数器(Program Conuter Register)
程序计数器是一块较小的内存空间,它是当前线程执行字节码的行号指示器,字节码解释工作器就是通过改变这个计数器的值来选取下一条需要执行的指令。它是线程私有的内存,也是唯一一个没有OOM异常的区域。
2、Java虚拟机栈区(Java Virtual Machine Stacks)
也就是通常所说的栈区,它描述的是Java方法执行的内存模型,每个方法被执行的时候都创建一个栈帧(Stack Frame),用于存储局部变量表、操作数栈、动态链接、方法出口等。每个方法被调用到完成,相当于一个栈帧在虚拟机栈中从入栈到出栈的过程。此区域也是线程私有的内存,可能抛出两种异常:如果线程请求的栈深度大于虚拟机允许的深度将抛出StackOverflowError;如果虚拟机栈可以动态的扩展,扩展到无法动态的申请到足够的内存时会抛出OOM异常。
3、本地方法栈(Native Method Stacks)
本地方法栈与虚拟机栈发挥的作用非常相似,区别就是虚拟机栈为虚拟机执行Java方法,本地方法栈则是为虚拟机使用到的Native方法服务。
4、堆区(Heap)
所有对象实例和数组都在堆区上分配,堆区是GC主要管理的区域。堆区还可以细分为新生代、老年代,新生代还分为一个Eden区和两个Survivor区。此块内存为所有线程共享区域,当堆中没有足够内存完成实例分配时会抛出OOM异常。
5、方法区(Method Area)
方法区也是所有线程共享区,用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译后的代码等数据。GC在这个区域很少出现,这个区域内存回收的目标主要是对常量池的回收和类型的卸载,回收的内存比较少,所以也有称这个区域为永久代(Permanent Generation)的。当方法区无法满足内存分配时抛出OOM异常。
6、运行时常量池(Runtime Constant Pool)
运行时常量池是方法区的一部分,用于存放编译期生成的各种字面量和符号引用。
垃圾收集(Garbage Collection)并不是Java独有的,最早是出现在Lisp语言中,它做的事就是自动管理内存,也就是下面三个问题:
1、什么时候回收
2、哪些内存需要回收
3、如何回收
1、什么时候回收?
上面说到GC经常发生的区域是堆区,堆区还可以细分为新生代、老年代,新生代还分为一个Eden区和两个Survivor区。
1.1 对象优先在Eden中分配,当Eden中没有足够空间时,虚拟机将发生一次Minor GC,因为Java大多数对象都是朝生夕灭,所以Minor GC非常频繁,而且速度也很快;
1.2 Full GC,发生在老年代的GC,当老年代没有足够的空间时即发生Full GC,发生Full GC一般都会有一次Minor GC。大对象直接进入老年代,如很长的字符串数组,虚拟机提供一个-XX:PretenureSizeThreadhold参数,令大于这个参数值的对象直接在老年代中分配,避免在Eden区和两个Survivor区发生大量的内存拷贝;
1.3 发生Minor GC时,虚拟机会检测之前每次晋升到老年代的平均大小是否大于老年代的剩余空间大小,如果大于,则进行一次Full GC,如果小于,则查看HandlePromotionFailure设置是否允许担保失败,如果允许,那只会进行一次Minor GC,如果不允许,则改为进行一次Full GC。
2、哪些内存需要回收
jvm对不可用的对象进行回收,哪些对象是可用的,哪些是不可用的?Java并不是采用引用计数算法来判定对象是否可用,而是采用根搜索算法(GC Root Tracing),当一个对象到GC Roots没有任何引用相连接,用图论的来说就是从GC Roots到这个对象不可达,则证明此对象是不可用的,说明此对象可以被GC。对于这些不可达对象,也不是一下子就被GC,而是至少要经历两次标记过程:如果对象在进行根搜索算法后发现没有与GC Roots相连接的引用链,那它将会第一次标记并且进行一次筛选,筛选条件是此对象有没有必要执行finalize()方法,当对象没有覆盖finalize()方法或者finalize()方法已经被虚拟机调用执行过一次,这两种情况都被视为没有必要执行finalize()方法,对于没有必要执行finalize()方法的将会被GC,对于有必要有必要执行的,对象在finalize()方法中可能会自救,也就是重新与引用链上的任何一个对象建立关联即可。
3、如何回收
选择不同的垃圾收集器,所使用的收集算法也不同。
在新生代中,每次垃圾收集都发现有大批对象死去,只有少量存活,则使用复制算法,新生代内存被分为一个较大的Eden区和两个较小的Survivor区,每次只使用Eden区和一个Survivor区,当回收时将Eden区和Survivor还存活着的对象一次性的拷贝到另一个Survivor区上,最后清理掉Eden区和刚才使用过的Survivor区,Eden和Survivor的默认比例是8:1,可以使用-XX:SurvivorRatio来设置该比例。
而老年代中对象存活率高,没有额外的空间对它进行分配担保,必须使用“标记-清理”或“标记-整理”算法。
Ⅲ 一、Android 虚拟机内存模型
jvm运行时数据区域解析
刘望舒的BLOG
虚拟机的内存模型
1.程序计数器: 确定程序指令执行顺序的,是唯一一块不会发生内存溢出的区域
2. Java虚拟机栈
**它也是线程私有的,负责存储方法内的局部变量,方法出口等。每执行一个方法都相当于压如一个栈帧,方法执行完比后这个栈帧从Java虚拟机栈中弹出。
3.本地方法区
负责管理虚拟机用到的 C 的方法。
4.堆内存区域
Java堆是一块被所有线程共享的区域,用来存放对象的实例。它不需要物理上连续,只需要逻辑上连续就可以。
5.方法区
方法区是被所有线程共享的的内存区域,用来存放已经被Java虚拟机加载的类的结构信息:运行时常量池,字段,方法信息,静态变量等数据。
class文件的内容
Ⅳ 重新理解jvm运行时的内存分布(堆栈方法区交互)
栈堆方法区的交互关系
java栈存储的本地变量表,包括八种数据类型和引用类型,引用类型指向对象的地址,保存在reference,指向java堆,对象类型数据会保存变量名,变量类型,变量值等,这些会存在方法区中去查看(在初始化的时候)。
在java栈中会存放对象实例(s1),但是他对象旁汪实例中具体的数据会由java栈中的引用指向java堆中的地址,里面的对象实例数据存放(实例名,实例相关类型,元数据信息。。。。),而静态变量,常量,类加载后的信息等会存放在方法区,在运行时需要调用的时候去方法区取,所以方法区和java堆都是共享的。而java栈时线程独有的数据(包括程序计数器,本地方法栈)。
一个jvm实例,只存在一个堆内存,堆内存的大小是可以调节的。类加载器读取了类文件之后,需要把类,方法,常量放到堆内存中,保存所有的引用类型的真实信息,以方便执行器执行。堆内存分为三部分。
(养老区就是老年代)
堆内存 逻辑上 分为三部:新生 +养老 +方法区
eden+survivor+Spaces(元空间或者叫方法区或者Perm)
Perm 永久存储区,是一个常驻内存的区域,用于存放jdk自身携带的Class,Interface的元数据,被装载进此区域的数据是不会被垃圾回收器回收的,只有关闭jvm后才会释放此区域所占用的内存。
如果出现OutOfMemoryReeor: PermGen space 说明java虚拟机堆永久带Perm内存设置不够,答袜一半出现这种情况,都是程序启动加载大量第三方jar呆滞的,
对于HotSpot虚拟机很多开发者习惯将方法区称之为永久代(Parmenent
Gen),永久代是方法区的一个实现,这是不对的,方法区是逻辑上的部分。在jdk7中已经将原本放在永久代的字符串常量池移走了。
常量池( Constant Pool Constant PoolConstant Pool Constant Pool Constant Pool )是方法区的一部分, Class Class文件除了有类的版本、 字段方法、接口等描述信息外,还有一项就是常量池这部分内容将在类加载后进入。
伊甸园区,所有对象刚new出来都会放在这里。
对象分两种:
1.如果是大对象直接分配在Old区。
2.如果禁言了逃逸分析,会运举仔在栈上分配。
以上两种都不符合,放入伊甸园区。(Eden区)
看java7中如图:
对比java8