美文网首页
2019 Java 底层面试题下半场

2019 Java 底层面试题下半场

作者: bullion | 来源:发表于2019-07-12 11:51 被阅读0次

    2019 Java 底层面试题上半场(第一篇) - 简书

    2019 Java 底层面试题上半场(第二篇) - 简书


    JVM底层原理



    JVM垃圾回收的时候如何确定垃圾?

        内存中已经不再被使用到的空间就是垃圾

    JVM常见的垃圾回收算法

    引用计数

    有对象引用+1,没对象引用-1,到0为止说明回收

    缺点: 

        1)每次对对象赋值时均要维护引用计数器,且计数器本身也有一定消耗。

        2)较难处理循环引用

    JVM的实现一般不采用这种方式

    复制算法

    Java堆从GC的角度还可以细分为:新生代(Eden区,From Survivor区和To Survivor区)和老年代(Old区)

    步骤:

        1)eden,SurvivorFrom复制到SurvivorTo,年龄+1

            首先,当Eden区满的时候会触发第一次GC,把还活着的对象拷贝到SurvivorFrom区,当Eden区再次触发GC的时候会扫描Eden区和From区域,对这两个区域进行垃圾回收,经过这次回收后还存活的对象则直接复制到To区(如果有对象的年龄已经达到了老年的标准,则赋值到老年代区),同时把这些对象的年龄+1

        2)清空eden,SurvivorFrom

            然后清空Eden和SurvivorFrom中的对象,也即复制之后有交换,谁空谁是To

        3)SuvivorTo和SurvivorFrom互换

            最后SurvivorTo和SurvivorFrom互换,原SurvivorTo成为下一次GC时的SurvivorFrom区,部分对象会在From和To区域中复制来复制去,如此交换15次(由JVM参数MaxTenuringThreshold决定,这个参数默认是15),最终如果还是存活就存入到老年代

    标记清除

    分成标记和清楚两个阶段,先标记出要回收的对象,然后统一回收这些对象

    缺点

        1)会导致内存碎片

    标记整理

    与标记清楚一样,再次扫描后往一端滑动存活对象

    缺点:

        1)移动对象需要成本


    如何判断一个对象是否可以被回收?

        1)上面讲的引用计数法

        2)枚举根节点做可达性分析(根搜索路径)

            为了解决引用计数法的循环引用问题,Java使用了可达性分析的方法。

            GC Roots 就是一组必须活跃的引用。基本思路就是通过一系列名为GC Roots的对象作为起始点,从这个被称为GC Roots的对象开始向下搜索,如果一个对象到GC Roots没有任何引用链相连时,则说明此对象不可用。


    Java中可以作为GC Roots的对象?

        1)虚拟机栈(栈帧中的局部变量区,也叫局部变量表)中引用的对象。

        2)方法区中的类静态属性引用的对象。

        3)方法区中常量引用的对象。

        4)本地方法栈中JNI(Native方法)引用的对象。

    JVM的参数类型

    标配参数

        java -version

        java -help

        java -showversion

    x参数

        -Xint    解释执行

        -Xcomp    第一次使用就编译成本地代码

        -Xmixed    混合模式

    xx参数(重点)

        Boolean类型

            公式:

                -XX:+ 或者 - 某个属性值

                + 表示开启 - 表示关闭

            例子:

                是否打印GC收集细节:

                    -XX:-PrintGCDetails

                    -XX:+PrintGCDetails

                是否使用串行垃圾回收器:

                    -XX:-UseSerialGC

                    -XX:+UseSerialGC

        KV设值类型

            公式:

                -XX:属性key=属性值value

            例子:

                元空间大小:

                    -XX:MetaspaceSize=128M

                年龄最大阈值(默认15)达到后进入年老代

                    -XX:MaxTenuringThreshold=15

    如何查看一个正在运行中的Java程序?它的某个jvm参数是否开启,具体值是多少?谈谈-Xms和-Xmx?

    jps -l    查看运行中的Java程序

    jinfo -flag PrintGCDetails 29833    查看29833进程的PrintGCDetails参数状态

    查看JVM所有出厂初始值?

    java -XX:+PrintFlagsInitial

    查看Xmx默认值?

    java -XX:+PrintFlagsFinal -version | grep MaxHeapSize

    查看JVM修改过和更新过的内容?

    java -XX:+PrintFlagsFinal

    = 等号表示初始值

    := 冒号等号表示JVM修改过或人为修改过后的值

    查看JVM默认垃圾回收器?

    java -XX:+PrintCommandLineFlags

    JVM的常用调优参数?

    -Xms    初始堆的大小,也是堆大小的最小值,默认值是总共的物理内存的1/64。等价于-XX:InitialHeapSize

    -Xmx    最大分配内存,默认为物理内存1/4。等价于-XX:MaxHeapSize

    -Xss    设置单个线程栈的大小,默认1M,等价于-XX:ThreadStackSize

    -Xmm    设置年轻代大小

    -XX:MetaspaceSize    元空间大小

    -XX:+PrintGCDetails

    -XX:SurvivorRatio

    -XX:NewRatio

    -XX:MaxTenuringThreshold


    JVM的-XX:PrintGCDetails参数?

    -XX:PrintGCDetails    输出GC的详细收集日志信息

    GC:

    Full GC:


    JVM的-XX:SurvivorRatio、-XX:NewRatio、-XX:MaxTenuringThreshold参数?

    -XX:SurvivorRatio

    -XX:SurvivorRatio    设置新生代中eden和S0/S1空间的比例

    默认:

    -XX:SurvivorRatio=8、Eden:S0:S1=8:1:1

    假如:

    -XX:SurvivorRatio=4、Eden:S0:S1=4:1:1

    SurvivorRatio值就是设置eden区的比例占多少,S0/S1相同

    -XX:NewRatio

    -XX:NewRatio    配置年轻代与老年代在堆结构的占比

    默认:

    -XX:NewRatio=2    新生代占1,老年代2,年轻代占整个堆的1/3

    假如:

    -XX:NewRatio=4    新生代占1,老年代4,年轻代占整个堆的1/5

    NewRatio值就是设置老年代的占比,剩下的1给新生代

    -XX:MaxTenuringThreshold 

    -XX:MaxTenuringThreshold    设置垃圾最大年龄,默认15,如果设置为0的话则年轻代对象不经过Survivor区,直接进入年老代。对于年老代比较多的应用,可以提高效率。如果将此值设置为一个较大值,则年轻代对象会在Survivor区进行多次复制,这样可以增加对象在年轻代的存活时间,增加在年轻代即被回收的概论。

    强引用、软引用、弱引用、虚引用分别是什么?

    强引用:

    当内存不足,JVM开始垃圾回收,对于强引用的对象,就算是出现了OOM(内存泄漏)也不会对该对象进行回收,即使对象以后永远都不会被用到JVM也不会回收。

    对与一个普通的对象,如果没有其他引用关系,或者显式地将相应的强引用赋值为null,一般认为就是可以被垃圾收集的了。

    强引用案例:

    public static void main(String[] args) {

            Object obj1 = new Object(); //这样定义的默认引用就是强引用

            Object obj2 = obj1; //obj2引用赋值

            obj1 = null;    //置空

            System.gc();

            System.out.println(obj2);

        }

    软引用:

    软应用需要用java.lang.ref.SoftReference类来实现。

    当系统内存充足时它不会被回收,当系统内存不足时它会被回收。

    软引用通常用在对内存敏感的程序中,比如高速缓存就有用到软引用,内存够用的时候就保留,不够用就回收。

    软引用案例:

    public class SoftReferenceDemo {

        /**

        * 内存够用的时候就保留,不够用就回收

        */

        public static void softRef_Memory_Enough(){

            Object o1 = new Object();

            SoftReference<Object> softReference = new SoftReference<Object>(o1);

            System.out.println(o1);

            System.out.println(softReference.get());

            o1 = null;

            System.gc();

            System.out.println(o1);

            System.out.println(softReference.get());

        }

        /**

        * JVM配置,故意产生大对象并配置小的内存,让它内存不够用了导致OOM,看软引用的回收情况

        * -Xms5m  -Xmx5m  -XX:+PrintGCDetails

        */

        public static void softRef_Memory_NotEnough(){

            Object o1 = new Object();

            SoftReference<Object> softReference = new SoftReference<Object>(o1);

            System.out.println(o1);

            System.out.println(softReference.get());

            o1 = null;

            try {

                byte[] bytes = new byte[30 * 1024 * 1024];

            } catch (Exception e) {

                e.printStackTrace();

            } finally {

                System.out.println(o1);

                System.out.println(softReference.get());

            }

        }

        public static void main(String[] args) {

            //softRef_Memory_Enough();

            softRef_Memory_NotEnough();

        }

    }

    弱引用:

    弱引用需要用java.lang.ref.WeakReference类来实现,它比软引用的生存期更短。

    对于只有弱引用的对象来说,只要垃圾回收机制一运行,不管JVM的内存空间是否足够,都会回收该对象占用的内存。

    弱引用案例:

    public static void main(String[] args) {

            Object o1 = new Object();

            WeakReference<Object> weakReference = new WeakReference<>(o1);

            System.out.println(o1);

            System.out.println(weakReference.get());

            o1 = null;

            System.gc();

            System.out.println(o1);

            System.out.println(weakReference.get());

        }

    虚引用:

    虚引用需要用java.lang.ref.PhantomReference类来实现。

    如果一个对象仅持有虚引用,那么它就和没有任何引用一样,在任何时候都可能被垃圾回收器回收,他不能单独使用也不能通过它访问对象,虚引用必须和引用队列(ReferenceQueue)联合使用。

    虚引用的主要作用是跟踪对象被垃圾回收的状态。设置虚引用的唯一目的就是在这个对象被收集器回收的时候收到一个系统通知或者后续添加进一步的处理。Java允许使用finalize()方法在垃圾收集器将对象从内存中清理出去之前做必要的清理工作。

    虚引用案例:

    public static void main(String[] args) throws Exception {

            Object o1 = new Object();

            ReferenceQueue<Object> referenceQueue = new ReferenceQueue<>();

            PhantomReference<Object> phantomReference = new PhantomReference<>(o1, referenceQueue);

            System.out.println(o1);

            System.out.println(phantomReference.get());

            System.out.println(referenceQueue.poll());

            System.out.println("======================================================");

            o1 = null;

            System.gc();

            Thread.sleep(500);

            System.out.println(o1);

            System.out.println(phantomReference.get());

            System.out.println(referenceQueue.poll());

        }

    引用队列:

    ReferenceQueue是用来配合引用工作的,没有ReferenceQueue一样可以运行。

    创建引用的时候可以指定关联的队列,当GC释放对象内存的时候,会将引用加入到引用队列。如果程序发现某个虚引用已经被加入到引用队列,那么就可以在所引用的对象内存被回收之前采取必要的行动。

    当关联的引用队列中有数据的时候,意味着引用指向的堆内存中的对象被回收。通过这种方式,JVM允许我们在对象被销毁后做一些我们自己想做的事情。

    引用队列案例:

    public static void main(String[] args) throws Exception {

            Object o1 = new Object();

            ReferenceQueue<Object> referenceQueue = new ReferenceQueue();

            WeakReference<Object> weakReference = new WeakReference<>(o1, referenceQueue);

            System.out.println(o1);

            System.out.println(weakReference.get());

            System.out.println(referenceQueue.poll());

            System.out.println("==============================================");

            o1 = null;

            System.gc();

            Thread.sleep(500);

            System.out.println(o1);

            System.out.println(weakReference.get());

            System.out.println(referenceQueue.poll());

        }


    软引用、弱引用的适用场景?

    假如有一个应用需要读取大量的本地图片:

        1)如果每次读取图片都从硬盘读取则会严重影响性能。

        2)如果一次性全部加载到内存中又可能造成内存溢出。

    此时使用软引用可以解决这个问题。

    设计思路是:用一个HashMap来保存图片的路径和相应图片对象关联的软引用之间的映射关系,在内存不足时,JVM会自动回收这些缓存图片对象所占用的空间,从而有效的避免了OOM的问题。

    Map<String, SoftReference<Bitmap>> imageCache = new HashMap<String, SofrReference<Bitmap>>();

    能谈谈WeakHashMap吗?

    WeakHashMap 继承于AbstractMap,实现了Map接口,它是一个HashMap的弱引用。

    案例:

    public class WeakHashMapDemo {

        public static void main(String[] args) {

            myHashmap();

            System.out.println("=============================");

            myWeakHashmap();

        }

        /**

        * Hashmap 强引用HashMap案例

        */

        private static void myHashmap(){

            HashMap<Integer, String> map = new HashMap<>();

            Integer key = new Integer(1);

            String value = "HashMap";

            map.put(key, value);

            System.out.println(map);

            key = null;

            System.out.println(map);

            System.gc();

            System.out.println(map + "\t" + map.size());

        }

        /**

        * WeakHashmap 弱引用HashMap案例

        */

        private static void myWeakHashmap(){

            WeakHashMap<Integer, String> map = new WeakHashMap<>();

            Integer key = new Integer(2);

            String value = "HashMap";

            map.put(key, value);

            System.out.println(map);

            key = null;

            System.out.println(map);

            System.gc();

            System.out.println(map + "\t" + map.size());

        }

    }


    请谈谈你对OOM的认识?

    java.lang.StackOverflowError

    导致原因:(栈空间溢出)

    public class StackOverflowErrorDemo {

        public static void main(String[] args) {

            stackOverflowError();

        }

        private static void stackOverflowError() {

            stackOverflowError();

        }

    }

    java.lang.OutOfMemoryError: java heap space

    导致原因:(堆空间溢出)

    -Xms10m -Xmx10m

    public class JavaHeapSpaceDemo {

        public static void main(String[] args) {

            byte[] bytes = new byte[80 * 1024 * 1024];

        }

    }

    java.lang.OutOfMemoryError: GC overhead limit exceeded

    导致原因:

    GC回收时间过长时会抛出OutOfMemoryError。过长的定义是,超过98%的时间用来做GC并且回收了不到2%的堆内存

    -Xms10m -Xmx10m -XX:+PrintGCDetails -XX:MaxDirectMemorySize=5m

    public static void main(String[] args) {

            int i = 0;

            List<String> list = new ArrayList<>();

            try {

                while(true){

                    list.add(String.valueOf(++i).intern());

                }

            }catch (Throwable e){

                System.out.println("**********i:" + i);

                e.printStackTrace();;

                throw e;

            }

        }

    java.lang.OutOfMemoryError: Direct buffer memory

    导致原因:

    写NIO程序经常使用ByteBuffer来读取或者写入数据,这是一种基于通道(channel)与缓冲区(Buffer)的I/O方式,他可以使用Native函数库直接分配堆外内存,然后通过一个存储在Java堆里面的DirectByteBuffer对象作为这块内存的引用进行操作。

    ByteBuffer.allocate(capability)分配JVM堆内存,属于GC管辖范围,由于需要拷贝所以速度相对较慢。

    ByteBuffer.allocateDirect(capability)分配OS本地内存,不属于GC管辖范围,由于不需要内存拷贝所以速度相对较快。

    如果不断分配本地内存,堆内存很少使用,那么JVM就不需要执行GC,DirectByteBuffer对象就不会被回收,这时候堆内存充足,但本地内存可能已经用光了,再次尝试分配本地内存就会出现OutOfMemoryError

    -Xms10m -Xmx10 -XX:+PrintGCDetails -XX:MaxDirectMemorySIze=5m

    public static void main(String[] args) {

            System.out.println("配置maxDirectMemory:"+(VM.maxDirectMemory() / (double) 1024 / 1024) + "MB");

            try{

                Thread.sleep(3000);

            }catch (InterruptedException e){

                e.printStackTrace();

            }

            //我们配置5MB 但实际使用6MB

            ByteBuffer bb = ByteBuffer.allocateDirect(6 * 1024 * 1024);

        }

    java.lang.OutOfMemoryError: unable to create new native thread

    导致原因:

    1.你的应用创建了太多线程,一个应用进程创建多个线程,超过系统承载极限。

    2.你的服务器并不允许你的应用程序创建这么多线程,Linux系统默认允许单个进程可以创建的线程数是1024个,你的应用创建超过这个数量,就会报:java.lang.OutOfMemoryError: unable to create new native thread)

    解决办法:

    1.想办法降低你应用程序创建线程的数量,分析应用是否真的需要创建这么多线程,如果不是,改动代码将线程数量降到最低。

    2.对于有的应用,确实需要创建很多线程,远超过Linux系统默认的1024个线程限制,可以通过修改Linux服务器配置,扩大Linux默认限制。

    查看修改Linux线程数

    vim /etc/security/limits.d/90-nproc.conf

    public static void main(String[] args) {

            for (int i = 0; ; i++) {

                System.out.println("********** i = " + i);

                new Thread(()->{

                    try {

                        Thread.sleep(Integer.MAX_VALUE);

                    }catch (InterruptedException e){

                        e.printStackTrace();

                    }

                }, ""+i).start();

            }

        }

    java.lang.OutOfMemoryError: Metaspace 

    导致原因:(元空间溢出)

    Java8及之后的版本使用Metaspace来替代永久代。

    Metaspace是方法区在HotSpot中的实现,它与持久代最大的区别在于:Metaspace并不在虚拟机内存中而是使用本地内存也即在java8中,被存储在叫做Metaspace的native memory。

    永久代(java8后被元空间取代了)存放了以下信息:

    虚拟机加载的类信息    常量池    静态变量    即时编译后的代码

    模拟Metaspace空间溢出,我们不断生成类汪元空间灌,类占据的空间总是会超过Metaspace指定的空间大小的。

    Linux看元空间大小

    java -XX:PrintFlagsInitial

    .... -XX:MetaspaceSize 就是元空间大小

    -XX:MetaspaceSize=8m -XX:MaxMetaspaceSize=8m

    public class MetaspaceOOMTest {

        static class OOMTest{

        }

        public static void main(String[] args) {

            int i = 0;//模拟计数多少次以后发生异常

            try {

                while(true){

                    i++;

                    Enhancer enhancer = new Enhancer();

                    enhancer.setSuperclass(OOMTest.class);

                    enhancer.setUseCache(false);

                    enhancer.setCallback(new MethodInterceptor() {

                        @Override

                        public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {

                            return methodProxy.invokeSuper(o, args);

                        }

                    });

                    enhancer.create();

                }

            }catch (Throwable e){

                System.out.println("**********多少次后发生了异常:" + i);

                e.printStackTrace();

            }

        }

    }


    四种GC垃圾收集器?

    串行垃圾回收器(UseSerialGC)

    它为单线程环境设计且只使用一个线程进行垃圾回收,会暂停所有的用户线程,所以不适合服务器环境。

    并行垃圾回收器(UseParallelGC)(默认是这个)

    多个垃圾收集线程并行工作,此时用户线程是暂停的,适用于科学计算/大数据处理等弱交互场景。

    并发垃圾回收器(UseConcMarkSweepGC)

    用户线程和垃圾收集线程同时执行(不一定是并行,可能是交替执行),不需要停顿用户线程,互联网公司多用它,适用对响应时间有要求的场景。

    G1垃圾回收器(UseG1GC)

    将内存分割成不同的区域然后并发的对其进行垃圾回收。


    如何查看默认的GC垃圾收集器?

    java -XX:+PrintCommandLineFlags -version

    默认垃圾收集器有哪些?

    java的gc回收的类型主要有几种:

    UseSerialGC,UseParallelGC,UseConcMarkSweepGC,UseParNewGC,UseParallelOldGC,UseG1GC

    Serial(串行):

    一个单线程的收集器,在进行垃圾收集的时候,必须暂停其他所有的工作线程直到它收集结束。

    对应的JVM参数是:-XX:+UseSerialGC

    -Xms10m -Xmx10m -XX:+PrintGCDetails -XX:+UseSerialGC

    开启后会使用:Serial(Young区) + Serial Old(Old区) 的收集器组合

    表示:新生代、老年代都会使用串行回收收集器,新生代使用复制算法,老年代使用标记-整理算法


    ParNew(并行):

    使用多线程进行垃圾回收,在垃圾收集时,会暂停其他所有的工作线程直到它收集结束,ParNew是新生代的并行,老年代还是串行。

    对应的JVM参数是:-XX:+UseParNewGC    启动ParNew收集器,只影响新生代的收集,不影响老年代

    开启后会使用:ParNew(Young区) + Serial Old(Old区) 的收集器组合

    表示:新生代使用复制算法,老年代使用标记-整理算法

    Parallel Scavenge(并行回收):

    Parallel Scavenge类似与ParNew但是是新生代和老年代都使用并行。

    对应的JVM参数是:-XX:+UseParallelGC 或 -XX:+UseParallelOldGC

    表示:新生代使用复制算法,老年代使用标记-整理算法

    多说一句:-XX:ParallelGCThreads=数字N    表示启动多少个GC线程

    cpu>8    N=5/8

    cpu<8    N=实际个数

    CMS(并发标记清除):

    Concurrent Mark Sweep 并发标记清除

    是一种以获取最短停顿时间为目标的收集器。适合应用在互联网网站或者B/S系统的服务器上,这类应用尤其重视服务器的响应速度,希望系统停顿时间最短。适合内存大、CPU多的服务器端应用,也是G1出现之前大型应用的首选收集器。

    对应的JVM参数是:-XX:+UseConcMarkSweepGC    开启该参数后会自动将-XX:+UseParNewGC打开

    开启后会使用:ParNew(Young区) + CMS(Old区) + Serial Old(Old区) 的收集器组合,Serial Old将作为CMS出错的后备收集器

    4步过程:

    初始标记(CMS initial mark)

        标记GC Roots能直接关联的对象,速度很快,仍然需要暂停所有的工作线程。

    并发标记(CMS concurrent mark)和用户线程一起

        进行GC Roots跟踪的过程,和用户线程一起工作,不需要暂停工作线程。主要标记过程,标记全部对象。

    重新标记(CMS remark)

        修正在并发标记期间,因用户程序继续运行而导致标记产生变动的那一部分对象的标记记录,仍然需要暂停所有工作线程。

        由于并发标记时,用户线程依然运行,因此在正式清理前,在做修正。

    并发清除(CMS concurrent sweep)和用户线程一起

        清除GC Roots不可达对象,和用户线程一起工作,不需要暂停工作线程。基于标记结果,直接清理对象。


    JVM的Server和Client模式分别是什么意思?

    1)32位Window操作系统,不论硬件如何都默认使用Client的JVM模式

    2)32位其他操作系统,2G内存同时有2个cpu以上用Server模式,低于该配置还是Client模式

    3)64位默认Server模式

    如何选择垃圾收集器?

    单CPU或小内存,单机程序

    -XX:+UseSerialGC

    多CPU,需要最大吞吐量,如后台计算型应用

    -XX:+UseParllelGC

    或者

    -XX:+UseParallelOldGC

    多CPU,追求低停顿时间,需快速响应如互联网应用

    -XX:+UseConcMarkSweepGC

    -XX:+ParNewGC


    G1垃圾收集器?

    G1是什么:

    G1是一种服务器端的垃圾收集器,应用在多处理器和大容量内存环境中,在实现高吞吐量的同时,尽可能的满足垃圾收集暂停时间的要求。

    以前收集器特点:

    1:年轻代和老年代是各自独立且连续的内存块

    2:年轻代收集使用单sden+S0+S1进行复制算法

    3:老年代收集必须扫描整个老年代区域

    4:都是以尽可能少而快速的执行GC为设计原则

    G1特点:

    1:能与应用程序线程并发执行

    2:整理空闲空间更快

    3:不希望牺牲大量的吞吐性能

    4:不需要更大的Java Heap

    G1收集器的设计目标是取代CMS收集器,它同CMS相比,在以下方面更出色:

    1:G1有整理内存过程,不会产生很多内存碎片

    2:G1的Stop The World(STW)更可控,G1在停顿时间上添加了预测机制,用户可以指定期望停顿时间

    为了解决CMS存在的内存碎片问题同时又保留CMS垃圾收集器低暂停的优点,发布了一个新的垃圾收集器G1。

    在JDK9中G1将变成默认的垃圾收集器以替代CMS

    特点:

    1:G1能充分利用多CPU、多核环境硬件优势,尽量缩短STW

    2:G1整体上采用标记清除算法,局部通过复制算法,不会产生内存碎片

    3:宏观上看G1之中不再区分年轻代和老年代。把内存划分成多个独立的子区域(Region),可以近似的理解为一个围棋的棋盘。

    4:G1收集器里面讲整个的内存区都混合在一起了,但其本身依然在小范围内要进行年轻代和老年代的区分,保留了新生代和老年代,但他们不再是物理隔离的,而是一部分Region的集合且不需要Region是连续的,也就是说依然会采用不同的GC方式来处理不同的区域。

    5:G1虽然也是分代收集器,但整个内存分区不存在物理上的年轻代和老年代的区别,也不需要完全独立的survivor(to space)堆做复制准备。G1只有逻辑上的分代概念,或者说每个分区都可能随G1的运行在不同代之间前后切换。

    G1底层原理?

    Region区域化垃圾收集器:

    最大好处是化整为零,避免全内存扫描,只需要按照区域来进行扫描即可。

    Region区域化垃圾收集器思想:

    区域化内存划片Region,整体编为了一些列不连续的内存区域,避免了全内存区的GC操作。

    核心思想是将整个堆内存区域分成大小相同的子区域(Region),在JVM启动时会自动设置这些子区域大小,在堆的使用上G1并不要求对象的存储一定是物理上连续的只要逻辑上连续即可,每个分区也不会固定的为某个代服务,可以按需在年轻代和老年代之间切换。启动时可以通过参数-XX:G1HeapRegionSize=n可指定分区大小(1MB~32MB,且必须是2的幂),默认将整堆划分为2048个分区。

    大小范围在1MB~32MB,最多能设置2048个区域,也即能够支持的最大内存为32MB * 2048 = 65536MB = 64G内存 

    G1算法:

    G1算法将堆划分为若干个区域(Region),它仍然属于分代收集器

    这些Region的一部分包含新生代,新生代的垃圾收集依然采用暂停所有应用线程的方式,将存活对象拷贝到老年代或者Survivor空间。

    这些Region的一部分包含老年代,G1收集器通过将对象从一个区域复制到另外一个区域,完成了清理工作。这就意味着,在正常的处理过程中,G1完成了堆的压缩(至少是部分堆的压缩),这样也就不会有CMS内存碎片问题的存在了。

    在G1中,还有一种特殊的区域,叫做Humongous(巨大的)区域如果一个对象占用的空间超过了分区容量的50%以上,G1收集器就认为这是一个巨型对象。这些巨型对象默认直接会被分配在老年代,但是如果他是一个短期存在的巨型对象,就会对垃圾收集器造成负面影响。为了解决这个问题,G1划分了一个Humongous区,它用来专门存放巨型对象。如果一个H区装不下一个巨型对象,那么G1会寻找连续的H分区来存储。为了能找到连续的H区,有时候不得不启动Full GC。

    G1回收步骤?

    G1收集器下的Young GC

    针对Eden区进行收集,Eden区耗尽后会被处触发,主要是小区域收集 + 形成连续的内存块,避免内存碎片

    Survivor区的数据移动到新Survivor区,加入出现Survivor区空间不够,Eden区数据会部分普升到Old区

    最后Eden区收拾干净了,GC结束,用户的应用程序继续执行。

    G1收集器常用参数配置?

    -XX:+UseG1GC

    -XX:G1HeapRegionSize=n    设置G1区域大小,值是2的幂,范围1MB到32MB。目标是根据最小的Java堆大小划分出约2048个区域。

    -XX:MaxGcPauseMillis=n    最大GC停顿时间,这是个软目标,JVM将尽可能(但不保证)停顿小于这个时间

    -XX:InitiatingHeapOccupancyPercent=n    堆占用了多少的时候就触发GC,默认为45

    -XX:ConcGCThreads=n    并发GC使用的线程数

    -XX:G1ReservePercent=n    设置作为空闲空间的预留内存百分比,以降低目标空间溢出的风险默认值是10%

    一般情况开发人员仅仅需要声明以下参数即可:

    开始G1 + 设置最大内存 + 设置最大停顿时间

    -XX:+UseG1Gc    -Xmx32g    -XX:MaxGCPauseMillis=100


    JVMGC结合SpringBoot微服务的生产部署和调参优化?

    java -server jvm的各种参数 -jar jar/war包名字

    例:

    java -server -Xms1024m -Xmx1024m -XX:+UseG1GC -jar test.war

    查看配置的参数

    jps -l

    jinfo -flags 端口号

    生产环境服务器变慢,诊断思路和性能评估谈谈?

    top和uptime 命令

    top    用于实时监测系统资源使用状况,按1可以显示几核CPU

    uptime    用于查看系统的负载信息

    主要看CPU 和 MEM内存 和 load average 负载均衡

    load average 如果三个值相加除以3乘以100%如果大于60%则系统压力过大

    vmstat 命令

    vmstat -n 2 3    对操作系统的虚拟内存、进程、CPU活动进行监控,第一个参数是采样的时间间隔数单位是秒,第二个参数是采样的次数

    - procs

        r:运行和等待CPU时间片的进程数,原则上1核的CPU运行队列不要超过2,整个系统的运行队列不能超过总核数的2倍,否则代表系统压力过大

        b:等待资源的进程数,比如正在等待磁盘I/O、网络I/O等

    - cpu

        us:用户进程消耗CPU时间百分比,us值高,用户进程消耗CPU时间多,如果长期大于50%,优化程序

        sy:内核进程小号的CPU时间百分比

        us + sy参考值为80%,如果us + sy大于80%,说明可能存在CPU不足

    - id:处于空闲的CPU百分比

    - wa:系统等待IO的CPU时间百分比

    - st:来自于一个虚拟机偷取的CPU时间的百分比

    mpstat 命令

    mpstat -P ALL 2 每两秒采样一次,显示各个CPU进程消耗数

    - idle    CPU空闲率 越高越好

    pidstat 命令

    pidstat -u 1 -p 进程编号    显示各个进程的cpu使用统计

    pidstat -p 进程编号 -r 2    显示各个进程的内存使用统计

    pidstat -d 2 -p 进程编号    示各个进程的IO使用情况

    free 命令

    free -m 查看内存

    应用程序可用内存/系统物理内存>70%    内存充足

    应用程序可用内存/系统物理内存<20%    内存不足,需要增加内存

    20%<应用程序可用内存/系统物理内存<70%    内存基本够用

    df 命令

    df -h    查看磁盘剩余空间

    iostat 命令

    iostat -xdk 2 3    磁盘I/O性能评估,每2秒取样一次,共取3次

    rkB/s每秒读取数据量kB

    wkB/s每秒写入数据量kB

    svctm I/O请求的平均服务时间,单位毫秒

    await I/O请求的平均等待时间,单位毫秒;值越小,性能越好

    util    一秒钟有百分几的时间用于I/O操作。接近100%时,表示磁盘带宽跑满,需要优化程序或者增加磁盘;

    rkB/s、wkB/s根据系统应用不同会有不同的值,但有规律遵循:长期、超大数据读写,肯定不正常,需要优化程序读取。

    svctm的值与await的值很接近,表示几乎没有I/O等待,磁盘性能好,如果await的值远高于svctm的值,则表示I/O队列等待太长,需要优化程序或更换更快的磁盘。

    ifstat 命令

    ifstat l    查看网络I/O

    加入生产环境出现CPU过高,请谈谈你的分析思路和定位?

    1.用top命令找出CPU占用最高的程序

    2. ps -ef或者jps进一步定位

    ps -ef|grep java|grep -v grep

    3.定位到具体的线程或者代码

    ps -mp 进程 -o THREAD,tid,time

    参数解释:

        -m    显示所有的线程

        -p pid    进程使用cpu的时间

        -o    该参数后是用户自定义格式

        tid    线程号

        time    运行时间

    4.将需要的线程ID转换为16进制格式(英文小写格式)

    printf "%x\n" 线程ID

    5.jstack 进程ID | grep tid(16进制线程ID小写英文) -A60

    A60    打印出前60行

    GitHub


    常用词含义

    watch:会持续收到该项目的动态

    fork:复制某个项目到自己的Github仓库中

    star:点赞

    clone:将项目下载至本地

    follow:关注作者

    in关键词限制搜索范围

    公式:关键字 in:name或description或readme

    xxx in:name    项目名包含xxx的

    xxx in:description    项目描述包含xxx的

    xxx in:readme    项目的readme文件中包含xxx的

    组合使用:xxx in:name,readme    搜索项目名或者readme中包含xxx的项目

    stars或fork数量关键词查找

    springboot stars:>=5000    查找stars数大于等于5000的springboot项目

    springcloud forks:>500    查找forks数大于500的springcloud项目

    springboot forks:100..200 stars:80..100    查找fork在100到200之间并且stars数在80到100之间的springboot项目

    awesome搜索

    awesome系列一般是用来收集学习、工具、书籍类相关的项目

    awesome redis    搜索优秀的redis相关的项目,包括框架、教程等

    #L数字 高亮显示代码

    1行    地址后面紧跟    #L数字

    多行    地址后面紧跟    #L数字-L数字

    项目内搜索

    英文 t

    https://help.github.com/en/articles/using-keyboard-shortcuts

    搜索某个地区的大佬

    公式:location:地区    language:语言

    location:beijing language:java    地区北京的Java方向的用户

    相关文章

      网友评论

          本文标题:2019 Java 底层面试题下半场

          本文链接:https://www.haomeiwen.com/subject/uarekctx.html