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、资源加载和性能:
饿汉式在类创建的同时就实例化一个静态对象出来,不管之后会不会使用这个单例,都会占据一定的内存,但是相应的,在第一次调用时速度也会更快,因为其资源已经初始化完成,
而懒汉式顾名思义,会延迟加载,在第一次使用该单例的时候才会实例化对象出来,第一次调用时要做初始化,如果要做的工作比较多,性能上会有些延迟,之后就和饿汉式一样了。