Java基础
面向对象
面向对象有哪些特性
1. 面向对象是一种对现实世界理解和抽象的方法,面向对象和面向过程都是开发的一种思想,面向过程关注的是开发的流程,而面向对象关注的是对象。
2. 面向对象的特性:封装、继承、多态。
(1)封装:我们在类中编写的方法就是对现实细节的一种封装,我们编写的一个类就是对数据和数据操作的封装,可以说封装就是隐藏一切可隐藏的东西,只向外界提供最简单的编程接口。
(2)继承:继承是从已有类得到继承信息创建新类的过程,通过继承子类可以得到父类中所有的方法和属性,私有的也可以继承但是没有访问权限,让系统具备延展性。
(3)多态:允许不同类对象对同一事物做出不同响应。多态的三大必要条件,必须要有继承,必须要有重写,必须要有父类引用指向子类对象。作用:提高代码重用性。
(4)抽象:抽象只关注对象有哪些属性和行为,并不关注行为的细节。
匿名内部类是否可以继承其他类,是否可以实现接口
匿名内部类是没有名字的内部类,不能继承其他类,但一个内部类可以作为一个接口由另一个内部类实现
普通类和抽象类的区别
- 相同点
- 普通类和抽象类中都可以有普通方法和属性。
- 不同点
- 抽象类中可以有抽象方法,普通类中不能有抽象方法
- 普通类继承抽象类必须重写抽象类中的全部抽象方法,或者将将类定义为抽象类
抽象类和接口的区别
- 相同点
- 抽象类和接口中都可以写抽象方法
- 抽象类和接口都不能被实例化
- 可以将抽象类和接口作为引用类型
- 不同点
- 抽象类中可以定义构造器,接口不可以
- 抽象类中可以存在普通方法,接口中不行
- 抽象类中可以存在属性,接口中不行
- 一个类只能继承一个抽象类,但可以实现多个接口
重载和重写的区别
-
相同点
- 重载和重写后的方法名和原方法名相同
-
不同点
- 重载要求方法名相同参数列表不同,重写要求方法名参数列表都相同
Java中如何跳出当前的多重嵌套循环
- break完全结束一个循环,执行循环后面的语句
- continue跳出当前循环,执行下一次循环
char类型中能否存储中文汉字,为什么
char类型可以存储一个中文汉字,因为Java中使用的编码是Unicode,一个char类型占2个字节,所以可以存中文
最有效的方法算出2*8等于几
2<<3
java的异常处理
- 异常种类
按照一场需要处理的时机分为运行时异常和编译时异常, - 处理异常的方法
- 当前方法知道如何处理异常,使用try…catch来捕获
- 当前不知道如何处理,则使用throw抛出异常
Error(错误)和Exception(异常)的区别
- 相同点
- Error和Exception都是Throwable的子类
- 不同点
- Error类一般是指与虚拟机相关的问题,如系统崩溃,虚拟机错误,内存不足,方法栈益处等。遇到这样的错误直接终止程序
- Exception类表示可以处理的异常,要么捕获要么抛出。
Java异常处理机制
Java中对异常进行了分类,不同类型的异常分别用不同的Java类表示,Exception 表示程序还能够克服和恢复的问题,异常又分为系统异常和普通异常,系统异常是程序员在编码过程中出现的问题导致的,可以通过修改代码来解决,普通异常是用户能够克服的异常,比如断网、本地硬盘不够等。
五个常见的运行时异常
(1)NullPointerException空指针异常;出现原因:调用了未初始化的对象或者不存在的对象。
(2)Class Not Found Exception指定的类找不到;出现原因:类的名称和路径错误,通常都是程序试图通过字符串来加载某个类时可能引发异常。
(3)NumberFormatException 字符串转换为数字异常;出现原因:字符型
数据中包含非数字型字符。
(4)IndexOutOfBoundsException 数组角标越界异常,常见于操作数组对
象时发生。
(5)IllegalArgumentException 方法传递参数错误。
(6)ClassCastException 数据类型转换异常。
(7)NoClassDefFoundException 未找到类定义错误。
(8)SQLException SQL 异常,常见于操作数据库时的 SQL 语句错误。
(9)InstantiationException 实例化异常。
(10)NoSuchMethodException 方法不存在异常。
throw和throws的区别
- throw
- throw语句用在方法体内,表示抛出异常由方法体内的语句处理
- throw是具体向外抛出异常的动作,抛出的是一个异常的实例,一般在后面跟上一个继承了Exception的类
- throws
- throws语句跟在方法声明的后面,表示抛出异常,由该方法的调用者来处理这个异常(向外抛)
- throws表示出现异常的一种可能性,并不一定会发生这种异常。
数组有没有 length() 方法?String 有没有 length() 方法?
数组没由length()方法但是由length属性,String由length()方法。
String 、StringBuffer、StringBuilder 的区别
- 相同点
- String和StringBuffer是线程安全的
- StringBuilder 与 StringBuffer 有公共父类 AbstractStringBuilder(抽象类)
2.不同点 - String的值是不可变的,而StringBuffer、StringBuilder的长度是可变的
- String和StringBuffer是线程安全,StringBuilder 线程不安全
- StringBuffer 支持多线程操作字符串,StringBuilder只能单线程操作字符串
Java中的IO流=
- 流的分类
- 按流的方向分:输入流(inputStream)、输入流(outputStream)。
- 按照实现功能分:节点流,如(FileReader)、处理流,如(BufferedReader)
- 按照处理数据的单位:字节流和字符流
java对象序列化
就像你寄一箱饼干,因为体积太大,就全压成粉末紧紧地一包寄出去,这就是序列化的作用。
- 序列化是干什么的
为了保存在内存中的各种对象的状态,并且可以把保存的对象状态再读出来,是一种将对象保存在内存中的机制 - 序列化有什么用
- 需要把一个对象保存到一个文件或者数据库中的时候需要用到序列化
- 需要用套接字在网络上传送对象的时候;
- 需要通过RMI传输对象的时候;
- 如何实现序列化
- 让类实现Serializable接口,该接口是一个标志性接口,标注该类对象是可被序列
- 然后使用一个输出流来构造一个对象输出流并通过writeObject(Obejct)方法就可以将实现对象写出
- 如果需要反序列化,则可以用一个输入流建立对象输入流,然后通过readObeject方法从流中读取对象
Java集合
ArrayList、HashSet、HashMap 是线程安全的吗?如果不是我想要线程安全的集合怎么办
ArrayList、HashSet、HashMap的底层源码中,每个方法都没有加锁,所以线程都不安全,如果需要线程安全的集合可以使用Vector和HashTable
ArrayList内部是怎么实现的
ArrayList的内部是用Object[](数组)实现的。当我们new一个ArrayList的时候,在内部调用了EmptyArray类的无参构造,当new ArrayList (参数),带参数的时候调用EmptyArray的有参构造,如果是int类型大于等于0的话就是Array List的长度。
并发集合和普通集合如何区别
- 常见的并发集合由ConcurrentHashMap、ConcurrentLinkedQueue、ConcurrentLinkedDeque,都是Concurrent包下的
- 在Java中由普通集合、同步(线程安全)集合、并发集合。普通集合性能最高,但是不保证线程安全和并发的可靠性,线程安全的集合仅仅是给集合添加了同步锁,缺牺牲了性能,而且并发的效率就更低了,并发集合通过复杂的策略不仅保证了多线程的安全又提高了并发时的效率
List的三个子类特点
- ArrayList:因为底层时数组实现的,所以查询快,增删慢
- LinkedList:底层时链表是西安的,所以查询慢,增删快
- Vector:底层是数组、线程安全的,所以增删慢、查询慢
List、Map、Set的区别
-
从结构上看
- List和Set时存储单列数据的集合,Map时存储键值对的双列数据集合
- List存储有序且允许重复,Map存储无序且不允许重复只能存在一个key为null,Set无序且只能有一个为空
-
从实现类上看
- List有三个实现类:ArrayList、LinkedList、Vector
- Map有三个实现类:HashMap、HashTable、LinkedHashmap
- HashMap:基于hash表的Map表的Map接口实现,线程不安全,效率高,支持null和null键
- HashTable:线程安全,低效,不支持空和空键
- LinkedMap:时HashMap的一个子类,保存了记录的插入顺序,默认升序
- Set有两个实现类:HashSet、LinkedHashSet
- HashSet底层是由HashMap实现,不允许值重复,使用时需要重写equals()和hashCode方法
- LinkedHashSet:继承于HashSet,同时又基于LinkedHashMap来实现,底层使用的时LinkedHashMap
3.. HashMap 和 HashTable 有什么区别
HashMap 是线程不安全的,HashMap 是一个接口,是 Map 的一个子接口,是将键映射到值得对象,不允许键值重复,允许空键和空值;由于非线程安全,HashMap 的效率要较HashTable 的效率高一些.HashTable 是线程安全的一个集合,不允许 null 值作为一个key 值或者 Value 值;HashTable 是 sychronize,多个线程访问时不需要自己为它的方法实现同步,而 HashMap 在被多个线程访问的时候需要自己为它的方法实现同步;
-
数组和链表的区别
- 数组
- 在内存中时连续存储的
- 优点:因为时连续存储的,内存地址连续,所以在查找数据的时候效率比较高
- 缺点:长度时固定的,在改变数据个数时效率比较低。
- 链表
- 动态申请内存空间,不需要像数组需要提前申请号内存大小,链表只要在需要的时候申请就好了
- 优点:根据需要来动态申请或删除内存空间,对数据的增加和删除比较灵活
- 缺点:无序,查询慢
- 数组
-
链表和数组使用场景
- 数组应用场景:数据比较少;经常做的运算是按序号访问数据元素;数组更容易实现,任何高级语言都支持;构建的线性表较稳定。
- 链表应用场景:对线性表的长度或者规模难以估计;频繁做插入删除操作;构建动态性比较强的线性表。
Iterater 和 ListIterator 之间有什么区别
- 我们可以使用 Iterator 来遍历 Set 和 List 集合,而 ListIterator 只能遍历 List。
- Iterator 只可以向前遍历,而 LIstIterator 可以双向遍历。
- ListIterator 从 Iterator 接口继承,然后添加了一些额外的功能,比如添加一个元
素、替换一个元素、获取前面或后面元素的索引位置。
Collection 和 Collections 的区别
- Collection 是集合类的上级接口,继承与他的接口主要有 Set 和 List.
- Collections 是针对集合类的一个帮助类,他提供一系列静态方法实现对各种集合的搜
索、排序、线程安全化等操作
数据结构
数组和链表的区别
- 结构
- 数组的长度是固定的,不适用于数据的增删操作
- 链表动态的进行地址分配,支持动态的增删操作
- 内存存储
- 数组在栈中分配空间
- 链表在堆上分配空间(使用new的都是在堆上分配空间)
- 访问方式
- 数组在内存中是连续存储的,因此可以使用下标进行遍历
- 链表在内存中是链式存储的,因此无法使用下表遍历,只能使用迭代器,查询效率要比数组低
快排算法
解决哈希冲突的方法
- 线性探测法
- 平方探测法
- 伪随机序列法
- 拉链法
B树
B树是平衡树,每个节点到叶子节点的高度都是相同的,这也保证了每个查询是稳定的
Set底层用的什么数据结构
set底层使用的是红黑树
红黑树:是一种平衡二叉查找树。一般红黑树需要满足以下性质
- 每个节点要么红,要么黑
- 根节点都是黑的
- 如果一个结点是红的,那么它的俩个儿子都是黑的。
- 对于任一结点而言,其到叶结点树尾端 NIL 指针的每一条路径都包含相同数目的黑结点。
红黑树的各种操作的时间复杂度是多少?
能保证在最坏情况下,基本的动态几何操作的时间均为 O(lgn)
红黑树相对于哈希表,在选择使用的时候有什么依据
- 选择因素:查找速度, 数据量, 内存使用,可扩展性。
总体来说,hash 查找速度会比 map 快,而且查找速度基本和数据量大小无关,属于常数级别;而 map 的查找速度是 log(n)级别。并不一定常数就比 log(n) 小,hash 还有 hash函数的耗时。需要考虑内存使用的情况下使用map
什么是二叉树
二叉树(Binary)是n(n≥0)个结点的有限集合,它的每个结点至多只有两棵子树。它或是空集,或是由一个根结点及两棵不相交的分别称作这个根的左子树和右子树的二叉树组成。
多线程
创建线程的方式
- 继承 Thread 抽象类,重写 run 方法
- 实现 Runnable 接口,重写 run 方法
- 实现 Callable 接口,重写 call 方法(有返回值)
线程互斥与同步
- 在引入多线程后,由于线程执行的异步性,会给系统造成混乱,特别是在急用临界资源时,如多个线程急用同一台打印机,会使打印结果交织在一起,难于区分。当多个线程急用共享变量,表格,链表时,可能会导致数据处理出错,因此线程同步的主要任务是使并发执行的各线程之间能够有效的共享资源和相互合作,从而使程序的执行具有可再现性。
- 当线程并发执行时,由于资源共享和线程协作,使用线程之间会存在以下两种制约关系
- 间接相互制约。一个系统中的多个线程必然要共享某种系统资源,如共享 CPU,共享 I/O 设备,所谓间接相互制约即源于这种资源共享,打印机就是最好的例子,线程 A 在使用打印机时,其它线程都要等待。
- 直接相互制约。这种制约主要是因为线程之间的合作,如有线程 A 将计算结果提供给线程 B作进一步处理,那么线程 B 在线程 A 将数据送达之前都将处于阻塞状态。
- 间接相互制约可以称为互斥,直接相互制约可以称为同步
多线程共享
- 多个线程行为一致,共同操作一个数据源。也就是每个线程执行的代码相同,可以使用同一个 Runnable 对象,这个 Runnable 对象中有那个共享数据,例如,卖票系统就可以这么做。
- 多个线程行为不一致,共同操作一个数据源。也就是每个线程执行的代码不同,这时候需要用不同的 Runnable 对象。例如,银行存取款。
wait 和 sleep 方法的不同?
最大的不同是在等待时 wait 会释放锁,而 sleep 一直持有锁。wait 通常被用于线程间交互,sleep 通常被用于暂停执行
synchronized 和 volatile 关键字的作用
- 一旦一个共享变量(类的成员变量、类的静态成员变量)被 volatile 修饰之后,那么
就具备了两层语义:- 保证了不同线程对这个变量进行操作时的可见性,即一个线程修改了某个变量的值,这新值对其他线程来说是立即可见的。
- 禁止进行指令重排序。
- volatile 本质是在告诉 jvm 当前变量在寄存器(工作内存)中的值是不确定的,需要从主存中读取;synchronized 则是锁定当前变量,只有当前线程可以访问该变量,其他线程被阻塞住。
常用的线程池有哪些?(线程池的种类)
- new SingleThreadExecutor:单线程池,此线程池保证所有任务的执行顺序按照任务的提交顺序执行。
- new FixedThreadPool:固定大小线程池,每次提交一个任务就创建一个线程,直到线程达到线程池的最大大小。
- new CachedThreadPool:可缓存的线程池,此线程池不会对线程池大小做限制,线程池大小完全依赖于操作系统(或者说 JVM)能够创建的最大线程大小。
- new ScheduledThreadPool:大小无限的线程池,此线程池支持定时以及周期性执行任务的需求。
谈谈你对线程池的理解
线程池就是事先将多个线程对象放到一个容器中,当使用的时候就不用 new 线程而是直接去池中拿线程即可,节省了开辟子线程的时间,提高的代码执行效率。
- 合理利用线程池能够带来三个好处。
- 降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗。
- 提高响应速度。当任务到达时,任务可以不需要等到线程创建就能立即执行。
- 提高线程的可管理性。线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控。
利用银行取款进行举例说明
三个线程 a、b、c 并发运行,b,c 需要 a 线程的数据怎么实现
根据问题的描述,我将问题用以下代码演示,ThreadA、ThreadB、ThreadC,ThreadA用于初始化数据 num,只有当 num 初始化完成之后再让 ThreadB 和 ThreadC 获取到初始化后的变量 num。
分析过程如下:
- 考虑到多线程的不确定性,因此我们不能确保 ThreadA 就一定先于 ThreadB 和ThreadC 前执行,就算 ThreadA 先执行了,我们也无法保证 ThreadA 什么时候才能将变量 num 给初始化完成。因此我们必须让 ThreadB 和 ThreadC 去等待 ThreadA 完成任何后发出的消息。
- 现在需要解决两个难题,一是让 ThreadB 和 ThreadC 等待 ThreadA 先执行完,二是 ThreadA 执行完之后给 ThreadB 和 ThreadC 发送消息。
- 解决上面的难题我能想到的两种方案,一是使用纯 Java API 的 Semaphore 类来控制线程的等待和释放,二是使用 Android 提供的 Handler 消息机制。
什么情况下导致线程死锁,遇到线程死锁该怎么解决
死锁:死锁是指多个线程因竞争资源而造成的一种僵局(互相等待),若无外力作用,这些进程都将无法向前推进
死锁产生的必要条件:线程需要请求的资源在一个时间段内被另一个线程占用,其他线程只能等待。
如何避免死锁:
- 加锁顺序(线程按照一定的顺序加锁)
- 加锁时限(线程尝试获取锁的时候加上一定的时限,超过时限则放弃对该锁的请求,并释放自己占有的锁
Java 中多线程间的通信怎么实现?
线程通信得方式:
- 共享变量:线程间通信可以通过发送信号,发送信号的一个简单方式是在共享对象的变量里设置信
号值。线程 A 在一个同步块里设置 boolean 型成员变量 hasDataToProcess 为 true,
线程 B 也在同步块里读取 hasDataToProcess 这个成员变量。 - wait/notify 机制:以资源为例,生产者生产一个资源,通知消费者就消费掉一个资源,生产者继续生产资
源,消费者消费资源,以此循环
线程和进程的区别
- 进程:具有一定独立功能的程序关于某个数据集合上的一次运行活动,是操作系统进行资源分配和调度的一个独立单位。
- 线程:是进程的一个实体,是 cpu 调度和分派的基本单位,是比进程更小的可以独立运行的基本单位。
- 特点:线程的划分尺度小于进程,这使多线程程序拥有高并发性,进程在运行时各自内存单元相互独立,线程之间内存共享,这使多线程编程可以拥有更好的性能和用户体验
注意:多线程编程对于其它程序是不友好的,占据大量 cpu 资源。
请说出同步线程及线程调度相关的方法?
- wait():使一个线程处于等待(阻塞)状态,并且解锁
- sleep():使一个正在运行的线程处于睡眠状态,是一个静态方法,调用此方法要处理InterruptedException 异常;
- notify():唤醒一个处于等待状态的线程,当然在调用此方法的时候,并不能确切的唤醒某一个等待状态的线程,而是由 JVM 确定唤醒哪个线程,而且与优先级无关。
- notityAll():唤醒所有处于等待状态的线程,该方法并不是将对象的锁给所有线程,而是让它们竞争,只有获得锁的线程才能进入就绪状态;
什么是阻塞队列?如何使用阻塞队列来实现生产者-消费者模型
java.util.concurrent.BlockingQueue 的特性是:当队列是空的时,从队列中获取或删除元素的操作将会被阻塞,或者当队列是满时,往队列里添加元素的操作会被阻塞。阻塞队列不接受空值,当你尝试向队列中添加空值的时候,它会抛出NullPointerException。阻塞队列的实现都是线程安全的,所有的查询方法都是原子的并且使用了内部锁或者其
他形式的并发控制。BlockingQueue 接口是 java collections 框架的一部分,它主要用于实现生产者-消费者问题。
并行和并发的区别
- 并发:交替执行:吃饭的时候停下来,打电话,打完电话再吃饭
- 并行:同时执行:吃饭的时候边吃边接电话。
守护线程是什么?
守护线程,专门用于服务其他的线程,如果其他的线程(即用户自定义线程)都执行完毕,连 main 线程也执行完毕,那么 jvm 就会退出(即停止运行)——此时,连jvm 都停止运行了,守护线程当然也就停止执行了。
JavaSE 高级
反射
谈谈你对反射的理解
通过反射可以获取到Java中需要反射的类中的属性和方法。
实现反射的三种方法
- Class.forName(“全类名”)
- 类名.Class()
- 对象.getClass()
动静态代理的区别,什么场景使用?
静态代理通常只代理一个类,动态代理是代理一个接口下的多个实现类
静态代理事先知道要代理的是什么,而动态代理不知道要代理什么东西,只有在运行时才知道。
AOP 编程就是基于动态代理实现的,比如著名的 Spring 框架、Hibernate 框架等等都是动态代理的使用例子。
Java的设计模式&回收机制
总体来说设计模式分为三大类(共23种)
- 创建型模式:工厂方法模式、抽象工厂模式、单例模式、建造者模式、原型模式
- 结构型模式:共七种:适配器模式、装饰器模式、代理模式、外观模式、桥接模式、组合模式、享元模式。
- 行为型模式,共十一种:策略模式、模板方法模式、观察者模式、迭代子模式、责任链模式、命令模式、备忘录模式、状态模式、访问者模式、中介者模式、解释器模式。
JVM 垃圾回收机制和常见算法
- 引用计数器算法(废弃)
- 根搜索算法(使用)
重点!!!谈谈 JVM 的内存结构和内存分配
-
Java内存模型
Java 虚拟机将其管辖的内存大致分三个逻辑部分:方法区(Method Area)、Java 栈和Java 堆。- 方法区是静态分配的,编译器将变量绑定在某个存储位置上,而且这些绑定不会在运行时改变。常数池,源代码中的命名常量、String 常量和 static 变量保存在方法区。
- Java Stack (栈)是一个逻辑概念,特点是后进先出。一个栈的空间可能是连续的,也可能是不连续的。最典型的 Stack 应用是方法的调用,Java 虚拟机每调用一次方法就创建一个方法帧(frame),退出该方法则对应的 方法帧被弹出(pop)。栈中存储的数据也是运行时确定的。
- Java 堆分配(heap allocation)意味着以随意的顺序,在运行时进行存储空间分配和收回的内存管理模型。堆中存储的数据常常是大小、数量和生命期在编译时无法确定的。Java 对象的内存总是在 heap 中分配。我们每天都在写代码,每天都在使用 JVM 的内存。
-
Java内存分配
- 基础数据类型直接在栈空间分配;
- 方法的形式参数,直接在栈空间分配,当方法调用完成后从栈空间回收;
- 引用数据类型,需要用 new 来创建,既在栈空间分配一个地址空间,又在堆空间分配对象的类变量;
- 方法的引用参数,在栈空间分配一个地址空间,并指向堆空间的对象区,当方法调用完后从栈空间回收;
- 局部变量 new 出来时,在栈空间和堆空间中分配空间,当局部变量生命周期结束后,栈空间立刻被回收,堆空间区域等待 GC 回收;
- 方法调用时传入的实际参数,先在栈空间分配,在方法调用完成后从栈空间释放;
- 字符串常量在 DATA 区域分配 ,this 在堆空间分配;
- 数组既在栈空间分配数组名称, 又在堆空间分配数组实际的大小!
Java 中引用类型都有哪些?(重要)
强引用,弱引用,软引用,虚引用
堆和栈的区别
-
申请方式
- 栈:由系统自动分配
- 堆需要程序员自己申请,并指明大小,在 c 中 malloc 函数,对于 Java 需要手动 new Object()的形式开辟
-
申请后系统的响应
- 栈:只要栈的剩余空间大于所申请空间,系统将为程序提供内存,否则将报异常提示栈溢出。
- 首先应该知道操作系统有一个记录空闲内存地址的链表,当系统收到程序的申请时,会遍历该链表,寻找第一个空间大于所申请空间的堆结点,然后将该结点从空闲结点链表中删除,并将该结点的空间分配给程序。另外,由于找到的堆结点的大小不一定正好等于申请的大小,系统会自动的将多余的那部分重新放入空闲链表中。
-
申请大小的限制
- 栈:是向低地址扩展的数据结构,是一块连续的内存的区域。这句话的意思是栈顶的地址和栈的最大容量是系统预先规定好的,在 WINDOWS 下,栈的大小是 2M(也有的说是 1M,总之是一个编译时就确定的常数),如果申请的空间超过栈的剩余空间时,将提示 overflow。因此,能从栈获得的空间较小。
- 堆:堆是向高地址扩展的数据结构,是不连续的内存区域。这是由于系统是用链表来存储的空闲内存地址的,自然是不连续的,而链表的遍历方向是由低地址向高地址。堆的大小受限于计算机系统中有效的虚拟内存。由此可见,堆获得的空间比较灵活,也比较大。
-
申请效率的比较
- stack:由系统自动分配,速度较快。但程序员是无法控制的。
- heap:由 new 分配的内存,一般速度比较慢,而且容易产生内存碎片,不过用起来最方便。
-
heap 和 stack 中的存储内容
- tack: 在函数调用时,第一个进栈的是主函数中后的下一条指令(函数调用语句的下一条可执行语句)的地址,然后是函数的各个参数,在大多数的 C 编译器中,参数是由右往左入栈的,然后是函数中的局部变量。注意静态变量是不入栈的。当本次函数调用结束后,局部变量先出栈,然后是参数,最后栈顶指针指向最开始存的地址,也就是主函数中的下一条指令,程序由该点继续运行。
- heap:一般是在堆的头部用一个字节存放堆的大小。堆中的具体内容有程序员安排
类加载机制
类什么时候会被初始化
- 类被实例化的时候,就是new对象的时候
- 访问某个类或接口的静态变量,或者对该静态变量赋值
- 调用类中的静态方法
- 反射
- 初始化一个类的子类
- JVM 启动时标明的启动类,即文件名和类名相同的那个类
Java中为什么会有垃圾回收机制
- 安全性考虑
- 减少内存泄露
- 减少程序员工作量
在开发中遇到过内存溢出么?原因有哪些?解决方法有那些?
内存泄露的原因
1. 内容中加载的数据量过大。如一次从数据库中取出过大量数据
2. 集合类中有对对象的引用,使用完后未清空,使得 JVM 不能回收;
3. 代码中存在死循环或循环产生过多重复的对象实体;
4. 使用的第三方软件中的 BUG;
5. 启动参数内存值设定的过小;
解决内存泄露的问题
1. 修改 JVM 启动参数,直接增加内存
2. 检查错误日志,查看“OutOfMemory”错误前是否有其它异常或错误。
3. 对代码进行走查和分析,找出可能发生内存溢出的位置。
一级缓存、二级缓存
一级缓存的作用域为sqlSession,第一次直接查数据库,第二次直接在SQL Session中找,但是一旦数据库进行了增删改操作,SQL Session中的东西就会被清空。
二级缓存:二级缓存何一级缓存的机制相同,但是作用域不同,二级缓存的作用域为namespance,namespance的范围更大一些,需要程序员手动开启