JVM内存方面常见的问题

134 阅读2分钟

前言

面试的时候常被问到的问题,今天就来做下复盘,并且以问答的形式来简答一下。

start

1、内存泄漏
内存泄漏字面上的意思就是内存不受管控了,一般是指内存不可以被回收也不可以被利用,一般什么情况下会发生内存泄漏?比如使用static修饰的变量,比如使用private static Map map = new HashMap();如果使用该map保存了一个临时对象,如果一个临时对象用完之后本来就没有用会被垃圾回收器回收,结果因为保存在了static修饰的map中,导致回收不了。
2、内存抖动
刚听到这个词我的第一反应是内存中数据丢失,结果看了相关资料才明白内存抖动就是频繁的内存申请和释放,呈现出的内存图像是一把锯齿。常见的场景就是创建一个数组,然后频繁的扩容。
3、死锁以及死锁的构造
死锁顾名思义就是无法解开的锁,一般有两层含义,一种一个线程持有锁后,长时间不释放,对于其他线程而言等待会超时,那么这种也会认为是死锁,比如mysql中超大事务的操作对表增加了表锁,导致其他线程不能操作该表。另一种就是多个线程相互等待对方释放锁,最终导致永远也不会释放锁。
构造一个死锁:如果两个变量第一个线程处理后第二个线程才能处理。那么下面的程序将会陷入死锁状态。

public class TestLock {

    public static void main(String[] args) throws InterruptedException {
        final int[] a = {0};
        final int[] b = {1};
        final CountDownLatch countDownLatch = new CountDownLatch(2);
        Lock lock = new ReentrantLock();
        for (final int[] i = {0}; i[0] <2; i[0]++) {
            int finalI = i[0];
            new Thread(new Runnable() {
                @Override
                public void run() {

                    lock.lock();
                    if (finalI == 0 && b[0] == 0 && lock.tryLock()) {
                        a[0]++;
                        lock.unlock();
                        System.out.println("释放锁1" + Thread.currentThread().getName());
                    }

                    if (finalI == 1 && a[0] == 1 && lock.tryLock()) {
                        b[0]--;
                        lock.unlock();
                        System.out.println("释放锁2" + Thread.currentThread().getName());
                    }

                    if (lock.tryLock()) {
                        countDownLatch.countDown();
                    }
                }
            }).start();
        }

        // 等待所有线程执行完毕
        countDownLatch.await();
    }
}

补充一个特殊的内存泄漏: 单例模式内存泄漏:

public class MySingleton {
    private static MySingleton instance = null;
    private ArrayList<String> myList;

    private MySingleton() {
        myList = new ArrayList<String>();
    }

    public static MySingleton getInstance() {
        if(instance == null) {
            instance = new MySingleton();
        }
        return instance;
    }

    public void addItem(String item) {
        myList.add(item);
    }
}

这是有个典型的单例模式,在这个例子中,MySingleton类使用了一个ArrayList来存储数据。每次调用addItem方法时,都会将一个新的String对象添加到ArrayList中。但是,MySingleton类仅在其第一次调用getInstance方法时进行实例化,这意味着所有addItem调用都是在同一个实例上进行的。

由于Java的垃圾回收机制是基于引用计数的,因此只要MySingleton实例存在,它所持有的ArrayList也会一直存在。这意味着,如果程序在不需要MySingleton实例时不显式地将其清空,那么ArrayList也将一直存在并占用内存,导致内存泄漏。

为了避免这种情况,我们可以选择在不需要MySingleton实例时,将其引用设置为null,强制Java垃圾回收机制回收相关的内存。例如:

    MySingleton instance = MySingleton.getInstance();
    // 使用MySingleton实例 
    instance = null;