JVM结构:



 




java 年轻代能否大于老年代 jvm年轻代老年代比例_java 年轻代能否大于老年代


 


 


线程共享:方法区、堆


线程私有:虚拟机栈、本地方发栈、程序计数器


 


堆:存储所有对象实例及数组


java 年轻代能否大于老年代 jvm年轻代老年代比例_java 年轻代能否大于老年代_02


  • 堆内存分为年轻代(Young Generation)占对空间1/3、老年代(Old Generation)占对空间2/3。
  • 年轻代又分为Eden和Survivor区。Survivor区由FromSpace和ToSpace组成。Eden区占大容量,Survivor两个区占小容量,默认比例是8:1:1。

 


 新生成的对象首先放到年轻代Eden区,当Eden空间满了,触发Minor GC,存活下来的对象移动到Survivor0区,Survivor0区满后触发执行Minor GC,Survivor0区存活对象移动到Suvivor1区,这样保证了一段时间内总有一个survivor区为空。经过多次Minor GC仍然存活的对象(每熬过一次GC,存活的对象年龄加1,当达到阈值时,默认15)移动到老年代,对象太大的也会直接移动到老年代。 老年代存储长期存活的对象,占满时会触发Major GC=Full GC,GC期间会停止所有线程等待GC完成,所以对响应要求高的应用尽量减少发生Major GC,避免响应超时。


  • Minor GC : 清理年轻代 
  • Major GC : 清理老年代,也等同于 Full GC , 因为收集老年代的时候往往也会伴随着升级年轻代
  • Full GC : 清理年轻代、老年代及永久代(JDK1.8之后是元空间)

所有GC都会停止应用所有线程。


 


-Xms/ -Xmx:


-Xms:    堆内存初始大小


-Xmx:  堆内存最大允许大小,一般不要大于物理内存的80%


  • Xmx 和 Xms设置为老年代存活对象的3-4倍,即FullGC之后的老年代内存占用的3-4倍
  • 永久代 PermSize和MaxPermSize设置为老年代存活对象的1.2-1.5倍。
  • 年轻代Xmn的设置为老年代存活对象的1-1.5倍。
  • 老年代的内存大小设置为老年代存活对象的2-3倍

    


法区:存储 类信息、常量、静态变量


                JDK1.8之前HotSpot虚拟机以永久代来实现方法区,JDK1.7开始逐步移除永久代, JDK1.8完全废除永久代,用元空间 (MetaSpace) 来替代, 他们最大区别是:元空间并不在JVM中,而是使用本地内存


            运行时常量池: 是方法区的一部分, 用于存放字面量 (interned strings) 和符号引用。


             符号引用包括:1、类的权限定名;2、字段名和属性;3、方法名和属性。


             永久代的垃圾回收主要包括类的卸载和常量池的回收,当没有一个对象引用一个常量时,该常量即可被回收,而类的卸载需满足3个条件: 该类的所有实例都被回收了,该类的ClassLoader被回收了,该类对应的java.lang.Class没有在任何地方被引用,在任何地方都无法通过反射来实例化一个对象


 


栈:在方法执行时用来存储局部变量表(Local Variable)、操作栈(Operand Stack)、动态链接(Dynamic Linking)、方法返回地址(Return Address)等信息


平时说的栈一般指局部变量表部分,局部变量表用来存放方法参数以及方法内定义的局部变量,存放基本数据类型和对象引用


 


本地方法栈:与虚拟机栈作用相似,区别在于 虚拟机栈执行的是Java方法(也就是字节码)服务,而本地方法栈则为虚拟机使用到的native方法服务,底层调用的c或者c++


 


程序计数器:线程上下文切换时用于存储线程执行的位置( 虚拟机字节码指令的地址


 


 


类加载:


 


类加载3个步骤:加载->链接->初始化


java 年轻代能否大于老年代 jvm年轻代老年代比例_java 年轻代能否大于老年代_03


 


类加载时机:(按需加载,即使用时才加载)


 


  • 创建类的实例,也就是new一个对象
  • 访问某个类或接口的静态变量,或者对该静态变量赋值
  • 调用类的静态方法
  • 反射(Class.forName("com.hcj.load"))
  • 初始化一个类的子类(会首先初始化子类的父类)

 


类加载器:


             启动类加载器(BootStrapClassLoader):负责加载java核心类库,由C++实现,加载扩展类和应用程序类加载器


             扩展类加载器(ExtensionClassLoader):加载jre/lib/ext目录下的类库,父类为启动类加载器(java中获取为null)


             系统类加载器(SystemClassLoader):也叫应用程序类加载器,加载自定义类,父类为扩展类加载器


 


类加载机制:


  • 全盘负责:所谓全盘负责,就是当一个类加载器负责加载某个Class时,该Class所依赖和引用其他Class也将由该类加载器负责载入,除非显示使用另外一个类加载器来载入。
  • 双亲委派:所谓的双亲委派,则是先让父类加载器试图加载该Class,只有在父类加载器无法加载该类时才尝试自己加载该类。在接到加载类的请求时,首先将加载任务委托给父加载器,依次递归,如果父加载器可以完成类加载任务,就成功返回;只有父加载器无法完成此加载任务时,才自己去加载。
  • 缓存机制。缓存机制将会保证所有加载过的Class都会被缓存,当程序中需要使用某个Class时,类加载器先从缓存区中搜寻该Class,只有当缓存区中不存在该Class对象时,系统才会读取该类对应的二进制数据,并将其转换成Class对象,存入缓冲区中。这就是为什么修改了Class后,必须重新启动JVM,程序所做的修改才会生效的原因。

 


双亲委派模式优点: 可以避免类的重复加载,保护程序安全, 防止核心API被篡改


 


GC:GC 作用于堆和方法区,垃圾回收 分为 标记阶段 清除阶段


 


垃圾标记的两种算法:


  •             引用计数算法:每个 对象都会有一个引用计数器属性,每当对象被引用一次就加1,引用失效时就减1。当为0的时候就判断对象不再被引用,即为垃圾,可回收。 但需要额外维护计数器属性,增加了空间和时间的开销,最重要的是无法解决循环引用问题
  •             可达性分析算法(java使用的标记算法):以GC Roots为起始点,往下找引用链,当一个对象到GC Roots没有任何引用链相连,即不可达,则被标记为垃圾,可回收。 为保证分析结果的准确性,必须在一个能保证一致性的快照中进行,因此GC时会STW(停止整个程序)

 


GC Roots都有哪些: 虚拟机栈中引用的对象(局部变量表)、方法区中类的静态属性引用的对象、方法区中常量引用的对象、本地方法栈中JNI(native方法)引用的对象


 


    判断一个对象是否可回收会进行至少两次标记:


1、如果对象到GC Roots没有引用链,则进行第一次标记


            2、判断对象是否有必要执行其finalize()方法:若对象没有重写 finalize()方法或已执行过一次,则对象被标记为不可达,若对象重写 finalize()方法且未执行则会丢入队列中由Finalizer线程触发其 finalize()方法的调用,若在 finalize()方法中对象又与GC Roots建立联系,则会被标记为可达,反之标记为不可达。对象的 finalize()方法只会被调用一次


        


     GC算法:


              


                标记-清除(Mark-Sweep)算法:


                    最基础的算法,后续的GC算法都是在其基础上改进而来。先通过可达性分析算法进行垃圾标记(在对象header里标记对象是否可达),然后对堆内存进行遍历,通过读取对象header信息来判断是否可回收


                     缺点:效率低、会产生内存碎片、STW会导致非常差的用户体验


 


              复制算法: 适用于存活对象较少、垃圾对象较多的场景,新生代(对象死亡率较高)采用复制算法


                    将内存分为大小相同的两块,每次使用其中一块,当这块内存用完了,将存活对象复制到另一块内存连续的空间上,然后清除这一块内存。


                    优点:减少了标记对象遍历的时间开销,解决了内存碎片问题


                    缺点:内存利用率低,只能使用一半的内存


                     


              标记-整理(Mark-Compact)算法:


                  在标记之后,将存活对象整理到内存的一端,按顺序排放,然后清理其他的空间


                    优点:相对于标记-清除算法,解决了内存碎片问题,相对于复制算法,提高了内存的利用率


                    缺点:效率低于复制算法,移动对象过程中,需要调整引用的地址 


 


            分代收集算法:    JVM采用的GC算法


                  不同对象生命周期不一样,因此可对不同生命周期的对象采用不同的回收算法,提高回收效率。


  •                      新生代生命周期短,存活率低,回收频繁, 采用复制算法来收集,
  •                      老年代生命周期长,存活率高,采用标记/清理算法或者标记/整理算法收集

 


    垃圾收集器:GC算法的具体实现


 


                  新生代收集器: Serial、ParNew(Serial的升级版,支持多线程)、Parallel Scavenge(复制算法)


                  老年代收集器:Serial Old(标记-整理)、Parallel Old(标记-整理)、CMS(标记-清除)


 


                     Serial GC、Parallel GC、 Concurrent Mark Sweep GC有什么不同:


  •                              Serial GC内存占用和并行开销小
  •                              Parallel GC吞吐量优先
  •                              Concurrent Mark Sweep GC停顿时间较少

 


                     CMS(Concurrent-Mark-Sweep):JDK9被废弃,可通过命令开启、JDK14被移除,不可用


                     


  •                       老年代收集器(初始标记时会扫描年轻代)
  •                       采用标记-清除算法
  •                       低延迟,并发收集,尽可能缩短GC停顿时间

 


                     工作原理:初始标记->并发标记->重新标记->并发清理


                     初始标记(Initial-Mark):所有用户线程会STW,这个阶段只会标记GC Roots直接关联的对象,标记完,结束STW,速度非常快


                     并发标记(Concurrent-Mark):从GC Roots直接关联的对象往下遍历所有关联对象,耗时较长,但不会STW,与垃圾收集线程并发运行  


                     重新标记(Remark):此阶段会修正并发标记阶段由于用户线程未停止导致标记产生变动的标记记录,这个阶段会STW,且相较于初始标记STW时间稍长


                     并发清理(Concurrent-Sweep):此阶段清理之前标记为死亡的对象,释放内存空间,由于不需要移动存活对象,所以可与用户线程并发运行


 


                      缺点:会产生内存碎片、在并发阶段会占用一定CPU资源导致吞吐量降低、无法处理实时产生的垃圾(并发标记阶段产生的新垃圾只能等到下一次GC清理)


 


    


                     G1(Garbage First):JDK7以后开始出现,但非默认垃圾回收器,可通过命令开启。JDK9以后的默认垃圾回收器,CMS的替代             


 


                                    将堆空间分为若干个区域(Region),这些区域包含年轻代和老年代 


 


  •                        用标记-清理及复制算法
  •                        区域化、增量式的收集器,保证停顿时间不会太长
  •                        既不仅是新生代收集器也不仅是老年代收集器,它都能回收
  •                        主要针对多核CPU及大容量内存(堆大小6G或更大)的机器

 


 


                    JDK1.7-JDK1.8:默认Parallel Scavenge(新生代)+ Parallel Old(老年代)垃圾收集器