复习---JVM相关

1、内存模型、Java内存模型和JVM内存结构、GC机制和原理;
    内存模型:
        为了保证共享内存的正确性(可见性、有序性、原子性),内存模型定义了共享内存系统中多线程程序读写操作行为的规范。通过这些规则来规范对内存的读写操作,从而保证指令执行的正确性。保证了并发场景下的一致性、原子性和有序性。
        内存模型解决并发问题主要采用两种方式:限制处理器优化使用内存屏障
复习---JVM相关
    java内存模型:
        Java内存模型(Java Memory Model ,JMM)就是一种符合内存模型规范的,屏蔽了各种硬件和操作系统的访问差异的,保证了Java程序在各种平台下对内存的访问都能保证效果一致的机制及规范。
         JMM是一种规范,目的是解决由于多线程通过共享内存进行通信时,存在的本地内存数据不一致、编译器会对代码指令重排序、处理器会对代码乱序执行等带来的问题。
复习---JVM相关
 
 
    JVM 内存结构(描述的是线程运行所设计的内存空间):
        JVM内存结构主要有五部分:堆内存、方法区、java虚拟机栈、程序计数器、本地方法栈
        堆内存:
            堆是是jvm虚拟机的主要内存区域。所有线程共享,在jvm虚拟机启动时创建。用来存储几乎所有的对象实例。Java 堆的是可以扩展的,通过 调整jvm的-Xmx 和 -Xms 控制堆大小。
         方法区:
              方法区被所有线程共享,存储加载的类信息、常量、静态变量等。方法区还包含运行常量池,常量池中主要存储编译生成的常量和引用。
          java虚拟机栈:
              java虚拟机栈是每个线程私有的,当线程被创建时栈也会被创建。 栈帧中存储了方法的局部变量表,操作数栈,动态连接,和方法返回地址等信息。并且局部变量表所需空间在编译期就确定并分配完成,方法运行期间不会改变。(补充:局部变量表中存放了原生类型(java的8中基本数据类型)、引用类型和returnAddress类型 )
          程序计数器:
              程序计数器是当前线程所执行的字节码的行号指示器,它会指出下一条将要执行的指令的地址,字节码解释器就是通过改变计数器的值来选取程序接下来执行的操作。每个线程都会有一个独立的程序计数器,并且程序计数器是唯一不会出现OOM( OutOfMemoryError,内存溢出 )的内存区域。跟GC也有关,引用计数。
          本地方法栈:
              本地方法栈与java虚拟机栈作用类似,区别在于java虚拟机栈执行java方法,本地方法栈执行Native方法。(补充:java无法访问操作系统底层,所以使用Native方法拓展功能,这也是java运行速度要比传统的c/c++慢一些的原因。当程序想使用底层主机平台的某个特性但是不能通过java访问或使用一个并不是java语言编写的库或加快程序的性能,将部分对时间要求快速的代码作为本地方法实现时就需要用到native去声明java方法。)
   
复习---JVM相关
        GC机制和原理:
            GC也就是java JVM的垃圾回收机制,根据一定的回收策略自动回收内存,永不停歇,进而保证JVM的内存空间够用,防止内存泄漏和溢出等问题。
            GC回收算法:
           · 标记-清除:分为“标记”和“清除”两个阶段,首先标记所有需要回收的对象,在标记完成时统一回收,是最基础的回收算法。缺点是效率不高并且清除之后会产生大量不连续的内存碎片,当程序后续运行中要给对象分配较大内存时会提前触发另一次垃圾收集。
            ·复制算法:将内存按照容量划分成大小相等的两块,每次只是用其中一块,当这块内存使用完了,把存活的对象复制到另一块内存中,然后把这块内存一次性清理掉。该算法实现简单,运行高效。只是将原本的内存缩小了一半并且持续复制生存期长的对象也会降低效率。
            ·分代(分区)垃圾回收:Java堆分为新生代和老年代,方法区作为永久代。在新生代中,每次垃圾收集时都发现有大批对象死去,只有少量存活,适用复制算法;而老年代中因为对象存活率高、没有额外空间对它进行分配担保,就要使用标记-清除或标记-整理算法来进行回收。
            ·标记-压缩:标记压缩是根据标记清除算法提出的一种算法,相同的是标记过程一样,不同的是标记完成之后,让所有存活的对象移向一端,然后清理掉另一端的内存空间。
        补充:jdk1.8以后移除了永久代,由于永久代内存经常不够用或发生内存泄漏,所以jvm开发者在jdk1.8中移除了永久代,转而用元空间代替。所谓元空间,本质跟永久代类似,最大的区别是元空间不存在jvm虚拟机中,而是使用的本地内存。元空间的大小仅受本地内存限制,但可以通过以下参数来指定元空间的大小:
        -XX:MetaspaceSize,初始空间大小,达到该值就会触发垃圾收集进行类型卸载,同时GC会对该值进行调整:如果释放了大量的空间,就适当降低该值;如果释放了很少的空间,那么在不超过MaxMetaspaceSize时,适当提高该值。
        -XX:MaxMetaspaceSize,最大空间,默认是没有限制的。
        -XX:MinMetaspaceFreeRatio,在GC之后,最小的Metaspace剩余空间容量的百分比,减少为分配空间所导致的垃圾收集次数;
        -XX:MaxMetaspaceFreeRatio,在GC之后,最大的Metaspace剩余空间容量的百分比,减少为释放空间所导致的垃圾收集次数。
 
2、GC分哪两种,Minor GC 和Full GC有什么区别?什么时候会触发Full GC?分别采用什么算法?
    GC分为Minor GC 和Full GC两种。
    Minor GC 和Full GC有什么区别:
        Minor GC是针对新生代(Eden区、 To Survivor区、 From Survivor区)的垃圾回收,等新生代的Eden区空间耗光的时候就会触发一次Minor GC,而存活下来的对象会被送到新生代的Survivor区(幸存者区,分为两个to&from)。当发生Minor GC时,Eden区和form指向的Survivor区存活的对象会通过标记-复制算法复制到to指向的Survivor区,然后进行垃圾回收,再然后交换form和to的指针,保证to指向的Survivor区是空的;
        补充:jvm虚拟机会记录Survivor区的对象一共被来回复制了几次,如果一个对象被复制了15( 虚拟机参数 -XX:+MaxTenuringThreshold)次,该对象就会晋升到老年代,如果Survivor区的空间占用达到50%( 虚拟机参数: -XX:TargetSurvivorRatio),复制次数较高的对象也会晋升到老年代。当在 Minor GC过程中,to指向的内存空间不足以保存Eden区和from指向的Survivor区存活的对象,那么多余的对象会晋升到老年代,这就是过早提升。弊端就是会导致老年代的“短命”对象增多,很可能在 Minor GC之后紧接着进行 Full GC,导致提升失败。
        Full GC是对整个堆内存进行垃圾回收,包括新生代、老年代、永久代等。Full GC本身不会先进行Minor GC,可以通过配置 -XX:+ScavengeBeforeFullGC(非CMS回收算法)、CMSScavengeBeforeRemark(CMS回收算法)让进行Full GC之前先进行一次Minor GC,进而提高老年代的GC速度。Full GC是单线程回收整个堆。
    触发Full GC的条件:
             System.gc()方法的调用默认触发 full GC;
             老年代内存空间不足;
             方法区内存空间不足;
 
    分别采用什么算法:
        Minor GC采用的是标记-复制算法;Full GC是对整个堆内存回收,分代回收,不同的代使用的算法不同。
3、JVM里的有几种classloader,为什么会有多种?
    类加载器是通过类名获取二进制字节流
    启动类加载器 bootstrap class loader
        启动类加载器主要是加载JVM自身所需要的类;负责加载/lib路径下的核心类库或者-Xbootclasspath参数指定的路径下的jar包;出于安全方面的考虑,该加载器只加载java、javax、sun等开头的包下的类。
        用C++语言实现,是虚拟机的一部分,没有父类。
 
    拓展类加载器 extensions class loader
         负责加载JAVA_HOME/lib/ext目录下或系统变量-Djava.ext.dir指定位路径中的类库。
        java语言实现,父类加载器是null。
 
    应用程序类加载器 application class loader
        负责加载系统类路径java -classpath或-D java.class.path 指定路径下的类库,一般是程序中默认的类加载器,通过ClassLoader.getSystemClassLoader()方法可以获取到该类加载器。
        java语言实现,父类加载器是ExtClassLoader。
 
    自定义类加载器 java.lang.class loader
         通过继承java.lang.ClassLoader类的方式。
         父类加载器为AppClassLoader。
    为什么要有多种类加载器:
        1.为了区分同名的类;
        2.更方便的加强类的能力。类加载器可以在load class时对class进行重写、覆盖,其实就可以对类进行功能性的增强或修改。
 
4、什么是双亲委派机制?介绍一些运作过程,双亲委派模型的好处;
    什么是双亲委派机制:
        如果一个类加载器需要加载类,他会先把这个请求委托给父类的加载器去执行,如果父类加载器还有父类加载器,那么这个请求再向上委托,直到到达最终顶层的引导(启动)类加载器,如果父类加载器可以完成类加载任务,就成功返回,如果父类加载器不能加载,子类加载器才会自己去加载,这就是双亲委派机制。注意,此处的父子关系并非java类继承关系。
    
    双亲委派模型的好处:
        首先这种层级关系可以避免类的重复加载,其次是安全,防止核心API被随意篡改。越基础的类交给越高级的加载器加载。
 
5、什么情况下我们需要破坏双亲委派模型?
        
涉及到SPI的加载动作。
    补充:什么是SPI?SPI全称Service Provider Interface,是jdk内置的服务提供发现机制(动态替换发现的机制)。
用户追求程序的动态性,OSGI环境下类加载器由双亲委派模型的树状结构发展为网状结构。
 
附加:常见的JVM调优方法有哪些?可以具体到调整哪个参数,调成什么值?