珍爱网面经 9.20

261

1.同一进程间的不同线程共享什么,不共享什么?

共享的资源有

a. 堆

由于堆是在进程空间中开辟出来的,所以它是理所当然地被共享的;

b. 全局变量

它是与具体某一函数无关的,所以也与特定线程无关;因此也是共享的

c. 静态变量

虽然对于局部变量来说,它在代码中是“放”在某一函数中的,但是其存放位置和全局变量一样,存于堆中开辟的.bss和.data段,是共享的

d. 文件等公用资源

进程打开的文件描述符、信号的处理器、进程的当前目录和进程用户ID与进程组ID,是共享的。

独享的资源有

a. 栈

栈是独享的。栈是保证线程独立运行所必须的。线程函数可以调用函数,而被调用函数中又是可以层层嵌套的,所以线程必须拥有自己的函数堆栈,使得函数调用可以正常执行,不受其他线程的影响

b. 寄存器 这个可能会误解,因为电脑的寄存器是物理的,每个线程去取值难道不一样吗?其实线程里存放的是副本,包括程序计数器PC

2.ArrayList 的扩容,比如new ArrayList(10),再放入20个元素,会扩容几次

Arraylist初始空间设置为10,当放入第5个时,会到达阈值0.5,扩容到15,到7时又会继续扩容至20,继续放入会继续扩容,所以一共会扩容3此

3.a=-2,a>>1的结果

-2 是1010,左移后是1001 所以是-1 -2 是1010,右移后是1100,所以是-4

4.Jvm如果一直是gcfull,会是什么问题,怎么定位及排查

FullGC是垃圾回收过程中一个非常重要的事件。当发生FullGC的时候,垃圾收集器为了回收更多的垃圾对象,它会从JVM的所有内存中回收垃圾(Young、Old、Perm、Metaspace)。FullGC有很多个步骤,有些步骤需要暂停所有的应用线程,在这个过程中JVM是无法处理用户的请求的。JVM会使用所有的CPU来做垃圾回收,因此CPU的利用率会非常高。

什么原因导致了连续的FullGC? 导致连续的FullGC主要只有一个原因:JVM的堆内存不够用或者是Perm、Metaspace内存不够用,说明应用需要比你分配的内存更多的内存。通俗地说就是小马拉大车。所以JVM必须要努力的去清理垃圾来给存活的对象分配空间。

可能是因为以下原因:

  • 1.可能是自从上次调优JVM的内存以后,应用的流量忽然变大了!可能是业务量增加了,用户数量变多了。

  • 2.在业务峰值期间应用创建了比平时更多的对象,可能你并没有对峰值时候的应用内存做调优,或者是说应用峰值时候的流量变大了。

如何来解决连续的FullGC? 主要有以下几个方法:

  • 1.增加JVM的堆内存。 因为连续的FullGC主要是由于内存不足引起的,增加JVM的堆内存可以解决这个问题。比如之前给应用分配的是2.5G的堆内存,现在增加到3G看能否解决问题,通过给JVM传递-Xmx参数可以设置JVM的最大堆内存。-Xmx3G就设置了JVM的最大堆内存是3G。如果还是没解决问题,一点点的继续增加JVM的堆内存。不要给JVM分配过多的堆内存,因为这会增加GC的停顿时间。

  • 2.增加Perm或者Metaspace内存

如果Perm区或者是Metaspace太小也会导致连续的FullGC,这种情况下就需要增大Perm或者是Metaspace的内存。

  • 3.增加更多的JVM实例 另一个解决此问题的办法就是增加更多的JVM实例。当有更多的JVM实例以后,应用流量就会分摊到这些实例上,单个JVM承担的流量就降低了,那么它上面需要创建的对象随之也变少了,此时可能就不会有连续的FullGC了。

4.jvm中的内存分配

XmnXmsXmxXss有什么区别

Xmn、Xms、Xmx、Xss都是JVM对内存的配置参数,我们可以根据不同需要区修改这些参数,以达到运行程序的最好效果。

-Xms 堆内存的最小大小,默认为物理内存的1/64

-Xmx 堆内存的最大大小,默认为物理内存的1/4

-Xmn 堆内新生代的大小。通过这个值也可以得到老生代的大小:-Xmx减去-Xmn

-Xss 设置每个线程可使用的内存大小,即栈的大小。在相同物理内存下,减小这个值能生成更多的线程,当然操作系统对一个进程内的线程数还是有限制的,不能无限生成。线程栈的大小是个双刃剑,如果设置过小,可能会出现栈溢出,特别是在该线程内有递归、大的循环时出现溢出的可能性更大,如果该值设置过大,就有影响到创建栈的数量,如果是多线程的应用,就会出现内存溢出的错误。

5.完成一个线程安全的高并发的单例模式

单例模式有以下特点:

  • 1、单例类只能有一个实例。
  • 2、单例类必须自己创建自己的唯一实例。
  • 3、单例类必须给所有其他对象提供这一实例。

一、懒汉式单例

[java] view plain copy

    //懒汉式单例类.在第一次调用的时候实例化自己   
    public class Singleton {  
        private Singleton() {}  
        private static Singleton single=null;  
        //静态工厂方法   
        public static Singleton getInstance() {  
             if (single == null) {    
                 single = new Singleton();  
             }    
            return single;  
        }  
    }  

Singleton通过将构造方法限定为private避免了类在外部被实例化,在同一个虚拟机范围内,Singleton的唯一实例只能通过getInstance()方法访问。

但是以上懒汉式单例的实现没有考虑线程安全问题,它是线程不安全的,并发环境下很可能出现多个Singleton实例,要实现线程安全,有以下三种方式,都是对getInstance这个方法改造,保证了懒汉式单例的线程安全

1、在getInstance方法上加同步


    public static synchronized Singleton getInstance() {  
             if (single == null) {    
                 single = new Singleton();  
             }    
            return single;  
    }  

2、双重检查锁定


    public static Singleton getInstance() {  
            if (singleton == null) {    
                synchronized (Singleton.class) {    
                   if (singleton == null) {    
                      singleton = new Singleton();   
                   }    
                }    
            }    
            return singleton;   
        }  

3、静态内部类


    public class Singleton {    
        private static class LazyHolder {    
           private static final Singleton INSTANCE = new Singleton();    
        }    
        private Singleton (){}    
        public static final Singleton getInstance() {    
           return LazyHolder.INSTANCE;    
        }    
    }    

这种比上面1、2都好一些,既实现了线程安全,又避免了同步带来的性能影响。

二、饿汉式单例


    //饿汉式单例类.在类初始化时,已经自行实例化   
    public class Singleton1 {  
        private Singleton1() {}  
        private static final Singleton1 single = new Singleton1();  
        //静态工厂方法   
        public static Singleton1 getInstance() {  
            return single;  
        }  
    }  

饿汉式在类创建的同时就已经创建好一个静态的对象供系统使用,以后不再改变,所以天生是线程安全的。

饿汉式和懒汉式区别

从名字上来说,饿汉和懒汉,

饿汉就是类一旦加载,就把单例初始化完成,保证getInstance的时候,单例是已经存在的了,

而懒汉比较懒,只有当调用getInstance的时候,才回去初始化这个单例。

另外从以下两点再区分以下这两种方式:

1、线程安全:

饿汉式天生就是线程安全的,可以直接用于多线程而不会出现问题,

懒汉式本身是非线程安全的,为了实现线程安全有几种写法,分别是上面的1、2、3,这三种实现在资源加载和性能方面有些区别。

2、资源加载和性能:

饿汉式在类创建的同时就实例化一个静态对象出来,不管之后会不会使用这个单例,都会占据一定的内存,但是相应的,在第一次调用时速度也会更快,因为其资源已经初始化完成

而懒汉式顾名思义,会延迟加载,在第一次使用该单例的时候才会实例化对象出来,第一次调用时要做初始化,如果要做的工作比较多,性能上会有些延迟,之后就和饿汉式一样了。