JVM内存结构:
JVM运行时内存区域划分 ?
先画出内存结构图,然后根据线程私有,线程共享分别介绍。详见:JVM成神之路-JVM内存结构
栈和堆分别主要存的数据是什么?
栈主要是局部变量表和操作数栈及帧数据区,主要存局部变量
堆主要存的是对象,成员变量
方法区主要类变量,存符号引用,静态变量,类信息,常量等
堆分为哪几块,比如说新生代老生代,那么新生代又分为什么?
内存溢出OOM和堆栈溢出SOE的示例及原因、如何排查与解决 ?
内存泄漏和内存溢出的区别:
- 申请内存后,无法释放已申请的内存空间,内存泄漏堆积的结果是内存溢出
- 内存溢出是指申请内存时,没有足够的内存空间供申请者使用
1.是否App中使用了大量的递归或无限递归(递归中用到了大量的建新的对象)
2.是否App中使用了大量循环或死循环(循环中用到了大量的新建的对象)
3.检查App中是否使用了向数据库查询所有记录的方法。即一次性全部查询的方法,如果数据量超过10万多条了,就可能会造成内存溢出。所以在查询时应采用“分页查询”。
4.检查是否有数组,List,Map中存放的是对象的引用而不是对象,因为这些引用会让对应的对象不能被释放。会大量存储在内存中。
5.检查是否使用了“非字面量字符串进行+”的操作。因为String类的内容是不可变的,每次运行"+"就会产生新的对象,如果过多会造成新String对象过多,从而导致JVM没有及时回收而出现内存溢出。
如String s1 = "My name";
String s2 = "is";
String s3 = "xuwei";
String str = s1 + s2 + s3 +.........;这是会容易造成内存溢出的
但是String str = "My name" + " is " + " xuwei" + " nice " + " to " + " meet you"; //但是这种就不会造成内存溢出。因为这是”字面量字符串“,在运行"+"时就会在编译期间运行好。不会按照JVM来执行的。
在使用String,StringBuffer,StringBuilder时,如果是字面量字符串进行"+"时,应选用String性能更好;如果是String类进行"+"时,在不考虑线程安全时,应选用StringBuilder性能更好。
6.使用 DDMS工具进行查找内存溢出的大概位置
StackOverflowError 与 OutOfMemoryError 的区别?
栈溢出实例:
一)、是否有递归调用
- StackOverflowError 是指栈请求的深度大于虚拟机规定的栈深度,此时内存空间可能还足够
- OutOfMemoryError 是指内存空间不足,无法分配内存
二)、是否有大量循环或死循环
三)、全局变量是否过多
四)、 数组、List、map数据是否过大
五)使用DDMS工具进行查找大概出现栈溢出的位置
排查工具:MAT,jconsole查看Java虚拟机当前状况,jvisualvm查看内存转储文件
Java在什么时候会出现内存泄漏?
Java内存泄漏的根本原因:长生命周期的对象持有短生命周期对象的引用就很可能发生内存泄漏,尽管短生命周期对象已经不再需要,但是因为长生命周期持有它的引用而导致不能被回收,这就是Java中内存泄漏的发生场景。
1、静态集合类引起内存泄漏:如果对象加入到集合 后,还必须从集合 中删除,最简单的方法就是将集合对象设置为null。
2、当集合里面的对象属性被修改后,再调用remove()方法时不起作用。
3、各种连接:finally中释放掉
4、单例模式:单例对象持有外部的引用,那么这个对象将不能被JVM正常回收,导致内存泄漏
Java中的大对象如何进行存储?
尽量让大对象直接进入持久代,而不是新生代;因为大对象会占有过多的新生代,会增加GC.
-XX:PetenureSizeThreshold 设置大对象直接进入年老代的阈值。当对象的大小超过这个值时,将直接在年老代分配。参数-XX:PetenureSizeThreshold 只对串行收集器和年轻代并行收集器有效,并行回收收集器不识别这个参数。
生产环境JVM设置,没有什么优化,比较通用。
-Xms2048m -Xmx2048m -Xmn256m -XX:PermSize=128M -XX:MaxNewSize=256m -XX:MaxPermSize=256m -XX:+UseParNewGC -XX:+UseConcMarkSweepGC -XX:CMSInitiatingOccupancyFraction=50 -XX:CMSFullGCsBeforeCompaction=5 -XX:+UseCMSCompactAtFullCollection -XX:-HeapDumpOnOutOfMemoryError -Djava.awt.headless=true -Dfile.encoding=UTF-8
直接内存如何管理的?
直接内存不属于虚拟机运行时数据区的一部分,也不是java虚拟机规范中定义的内存区域。
这部分也被频繁的使用,而且也可能出现OOM异常。
JDK1.4中加入NIO,可用于Native函数库直接分配堆外内存,然后通过存储在Java堆中的DirectByteBuffer对象作为这块内存的引用进行操作;提高新能,避免在java堆和Native堆中来回复制数据
优点:减少垃圾回收,减少复制过程,提高性能
缺点:失去jvm管理内存的可见性,排查问题困难
char和double的字节,以及在内存的分布是怎样?
char:2byte;double:8byte。
浮点是一种对于实数的近似值数值表现法,由一个有效数字(即尾数)加上幂数来表示,通常是乘以某个基数的整数次指数得到。
new一个包含多个字符串的数组,这句代码执行时,内存的哪些地方有变化?
https://blog.csdn.net/w372426096/article/details/89070686
sting s=new string("abc")分别在堆栈上新建了哪些对象?
字符串的分配,和其他的对象分配一样,耗费高昂的时间与空间代价。JVM为了提高性能和减少内存开销,在实例化字符串常量的时候进行了一些优化。为了减少在JVM中创建的字符串的数量,字符串类维护了一个字符串池,每当代码创建字符串常量时,JVM会首先检查字符串常量池。如果字符串已经存在池中,就返回池中的实例引用。如果字符串不在池中,就会实例化一个字符串并放到池中。Java能够进行这样的优化是因为字符串是不可变的,可以不用担心数据冲突进行共享。
- 先在栈中开辟空间存放引用s;
- 堆中开辟空间存放一个新建String对象“abc”
- 引用s指向堆中新建的String对象
- s所指代的对象为堆中的地址,堆空间存放的是常量池里“abc”的地址
JVM内存模型:
说说线程安全问题,什么是线程安全,如何保证线程安全 ?
某个函数、函数库在并发环境中被调用时,能够正确地处理多个线程之间的共享变量,使程序功能正确完成。
1、并发
2、多线程
3、共享变量
(私密)https://blog.csdn.net/w372426096/article/details/84793226
并发编程,为了保证数据的安全,需要满足以下三个特性:
原子性是指在一个操作中就是cpu不可以在中途暂停然后再调度,既不被中断操作,要不执行完成,要不就不执行。
可见性是指当多个线程访问同一个变量时,一个线程修改了这个变量的值,其他线程能够立即看得到修改的值。
有序性即程序执行的顺序按照代码的先后顺序执行。
Java内存模型JMM ?
为了保证共享内存的正确性(可见性、有序性、原子性),内存模型定义了共享内存系统中多线程程序读写操作行为的规范。通过这些规则来规范对内存的读写操作,从而保证指令执行的正确性。它与处理器有关、与缓存有关、与并发有关、与编译器也有关。他解决了CPU多级缓存、处理器优化、指令重排等导致的内存访问问题,保证了并发场景下的一致性、原子性和有序性。
内存模型解决并发问题主要采用两种方式:限制处理器优化和使用内存屏障。本文就不深入底层原理来展开介绍了,感兴趣的朋友可以自行学习。
Java内存模型(Java Memory Model ,JMM)就是一种符合内存模型规范的,屏蔽了各种硬件和操作系统的访问差异的,保证了Java程序在各种平台下对内存的访问都能保证效果一致的机制及规范。
JMM里边的原子性、可见性、有序性是如何体现出来的,JMM中内存屏障是什么意思?
在Java中提供了一系列和并发处理相关的关键字,比如volatile、synchronized、final、concurren包等。其实这些就是Java内存模型封装了底层的实现后提供给程序员使用的一些关键字。
volatile是通过内存屏障来来禁止指令重排的进而保证有序型的。
内存屏障(Memory Barrier)是一类同步屏障指令,是CPU或编译器在对内存随机访问的操作中的一个同步点,使得此点之前的所有读写操作都执行后才可以开始执行此点之后的操作。下表描述了和volatile有关的指令重排禁止行为:
内存屏障也是保证可见性的重要手段,操作系统通过内存屏障保证缓存间的可见性,JVM通过给volatile变量加入内存屏障保证线程之间的可见性。
Java中的内存屏障:用于控制特定条件下的重排序和内存可见性问题。Java编译器也会根据内存屏障的规则禁止重排序。
讲下JVM的大页模式,JVM内存模型?
内存使用性能优化
https://blog.csdn.net/xihuanyuye/article/details/83930703
进程间共享内存的方式有哪些?
1.无名管道( pipe ):管道是一种半双工的通信方式,数据只能单向流动,而且只能在具有亲缘关系的进程间使用。进程的亲缘关系通常是指父子进程关系。
2.高级管道(popen):将另一个程序当做一个新的进程在当前程序进程中启动,则它算是当前程序的子进程,这种方式我们成为高级管道方式。
3.有名管道 (named pipe) : 有名管道也是半双工的通信方式,但是它允许无亲缘关系进程间的通信。
4.消息队列( message queue ) : 消息队列是由消息的链表,存放在内核中并由消息队列标识符标识。消息队列克服了信号传递信息少、管道只能承载无格式字节流以及缓冲区大小受限等缺点。
5.信号量( semophore ) : 信号量是一个计数器,可以用来控制多个进程对共享资源的访问。它常作为一种锁机制,防止某进程正在访问共享资源时,其他进程也访问该资源。因此,主要作为进程间以及同一进程内不同线程之间的同步手段。
6.信号 ( sinal ) : 信号是一种比较复杂的通信方式,用于通知接收进程某个事件已经发生。
7.共享内存( shared memory ) :共享内存就是映射一段能被其他进程所访问的内存,这段共享内存由一个进程创建,但多个进程都可以访问。共享内存是最快的 IPC 方式,它是针对其他进程间通信方式运行效率低而专门设计的。它往往与其他通信机制,如信号两,配合使用,来实现进程间的同步和通信。
8.套接字( socket ) : 套解字也是一种进程间通信机制,与其他通信机制不同的是,它可用于不同机器间的进程通信。
Synchronized:
synchronized 实现原理?
对象监视器
对于同步方法:JVM采用ACC_SYNCHRONIZED标记符来实现同步。 在Class文件的方法表中将该方法的access_flags字段中的synchronized标志位置1,表示该方法是同步方法并使用调用该方法的对象或该方法所属的Class在JVM的内部对象表示Klass做为锁对象。
对于同步代码块:JVM采用monitorenter、monitorexit两个指令来实现同步。
https://blog.csdn.net/w372426096/article/details/80077516
synchronized 与 lock 的区别 ?
- 首先synchronized是java内置关键字,在jvm层面,Lock是个java类;
- synchronized无法判断是否获取锁的状态,Lock可以判断是否获取到锁;
- synchronized会自动释放锁(a 线程执行完同步代码会释放锁 ;b 线程执行过程中发生异常会释放锁),Lock需在finally中手工释放锁(unlock()方法释放锁),否则容易造成线程死锁;
- 用synchronized关键字的两个线程1和线程2,如果当前线程1获得锁,线程2线程等待。如果线程1阻塞,线程2则会一直等待下去,而Lock锁就不一定会等待下去,如果尝试获取不到锁,线程可以不用一直等待就结束了;
- synchronized的锁可重入、不可中断、非公平,而Lock锁可重入、可判断、可公平(两者皆可)
- Lock锁适合大量同步的代码的同步问题,synchronized锁适合代码少量的同步问题。
1、synchronized是重量级锁? 从JDK 1.5 到 JDK 1.6 有一个高效并发方面的重要改进,HotSpot虚拟机开发团队在这个版本中花费了很大的精力去对Java中的锁进行优化(synchronized),如适应性自旋、锁消除、锁粗化、轻量级锁和偏向锁等。这些技术都是为了在线程之间更高效的共享数据,以及解决竞争问题。 在使用synchronized的时候,会有一个顺序,这个顺序叫做膨胀。会首先使用偏向锁尝试、然后使用轻量级锁、最后才会尝试重量级锁。
2、concurrent包为啥不被称之为重量级锁 concurrent包底层确实如你所说,依赖LockSupport 提供park()和unpark()方法实现的。 但是,这并不是说只要使用concurrent包中的类,那么就会直接使用park()和unpark()。上面还有一层重要的东西,那就是CAS。
3、 所以结论是。
synchronized和concurrent包,在底层和操作系统交互时,都是使用mutex lock的。但是,二者都会尝试各种锁优化来尽量避免走到这最后一步。 synchronized使用的是如锁膨胀等技术。 concurrent包则使用的是CAS技术。 在早期,synchronized没做优化的时候,我们称之为重量级锁。后面有了优化手段之后,我们还是沿用老的叫法,最终会膨胀成重量级锁。 而在concurrent包刚出来的时候,就采用了CAS等技术做了优化,所以,和那时候的synchronized相比,他就没有被称之为重量级锁了。
synchronized关键字锁住的是什么东西?在字节码中是怎么表示的?在内存中的对象上表现为什么?
具体原理是monitor
所得是对象实例或者类实例。
一个线程执行临界区代码过程如下:
1 获得同步锁
2 清空工作内存
3 从主存拷贝变量副本到工作内存
4 对这些变量计算
5 将变量从工作内存写回到主存
6 释放锁
wait/notify/notifyAll⽅法需不需要被包含在synchronized块中?这是为什么?
为什么wait, notify 和 notifyAll这些方法不在thread类里面?要在同步块中被调用?
要说明为什么把这些方法放在Object类里是有意义的,还有不把它放在Thread类里的原因。一个很明显的原因是JAVA提供的锁是对象级的而不是线程级的,每个对象都有锁,通过线程获得。如果线程需要等待某些锁那么调用对象中的wait()方法就有意义了。如果wait()方法定义在Thread类中,线程正在等待的是哪个锁就不明显了。简单的说,由于wait,notify和notifyAll都是锁级别的操作,所以把他们定义在Object类中因为锁属于对象。
sync原理详细,sync内抛异常会怎样,死锁吗?还是释放掉?怎么排查死锁?死锁会怎样?有没有什么更好的替代方案?
同步代码块中抛异常会马上释放锁在finally lock中释放,不会死锁。
ps或jps查看进程id
jstack查看dump文件寻找死锁问题
Volatile:
volatile 实现原理
禁止指令重排、刷新内存
https://blog.csdn.net/w372426096/article/details/81625055
锁:
重入锁的概念,重入锁为什么可以防止死锁
产生死锁的四个条件(互斥、请求与保持、不剥夺、循环等待)
如何检查死锁(通过jConsole检查死锁)
偏向锁、轻量级锁、重量级锁、自旋锁的概念
乐观悲观锁的设计,如何保证原子性,解决的问题;
AQS原理,ReentranLock源码,设计原理,整体过程。
继续聊多线程源码,sync原理,然后一个场景设计题;
多线程:
AQS同步队列
CAS无锁的概念、乐观锁和悲观锁
常见的原子操作类
什么是ABA问题,出现ABA问题JDK是如何解决的
乐观锁的业务场景及实现方式
Java 8并法包下常见的并发类
AtomicInteger底层实现原理;
synchronized与ReentraLock哪个是公平锁;
CAS机制会出现什么问题;
垃圾回收:
为什么新生代内存需要有两个Survivor区?
可以利用复制清楚的垃圾算法:
优点:不用考虑内存碎片等。复杂情况,只要移动堆顶指针,按顺序分配内存即可,实现简单,运行高效。只是这种算法的代价是将内存缩小为了原来的一半,未免太高了一点。
缺点:复制收集算法在对象存活率较高时就要进行较多的复制操作,效率将会变低