面试总结------Java内存管理与多线程

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/jianyuerensheng/article/details/53155974

Java内存管理与多线程

1. 什么是线程?什么是进程?同一进程下的线程共享

线程:程序在执行过程中,能够执行程序代码的一个执行单元,一个线程可以创建和撤销另一个线程;同一个进程中的多个线程之间可以并发执行。在Java语言中有4种状态:运行、就绪、挂起、结束。

进程:指一段正在执行的程序。线程有时也被称为轻量级进程,它是程序执行的最小单元,一个进程可以拥有多个线程,各个线程之间共享程序的内存空间及一些进程级的资源,但是各个线程拥有自己的栈空间。

进程的作用和定义:进程是为了提高CPU的执行效率,减少因为程序等待带来的CPU空转以及其他计算机软硬件资源的浪费而提出来的。进程是为了完成用户任务所需要的程序的一次执行过程和为其分配资源的一个基本单位,是一个具有独立功能的程序段对某个数据集的一次执行活动。

线程和进程的区别:
A. 地址空间和其它资源:进程间相互独立,同一进程的各线程间共享。某进程内的线程在其它进程不可见。
B. 通信:进程间通信IPC,线程间可以直接读写进程数据段(如全局变量)来进行通信——需要进程同步和互斥手段的辅助,以保证数据的一致性。
C. 调度和切换:线程上下文切换比进程上下文切换要快得多。
D. 在多线程OS中,进程不是一个可执行的实体。
E. 线程是进程的一部分,所以线程有的时候被称为是轻权进程或者轻量级进程。

线程与进程资源分配:
线程共享的内容包括: 进程代码段、进程的公有数据(利用这些共享的数据,线程很容易的实现相互之间的通讯)、进程打开的文件描述符、信号的处理器、进程的当前目录和、进程用户ID、进程组ID。
线程独有的内容包括: 线程ID 、寄存器组的值 、线程的堆栈 、错误返回码 、线程的信号屏蔽码 。

2. Java的内存机制

Java 把内存划分成两种:一种是栈内存,另一种是堆内存。
堆和栈相同点:
栈(stack)与堆(heap)都是Java用来在Ram中存放数据的地方。与C++不同,Java自动管理栈和堆,程序员不能直接地设置栈或堆。
堆和栈区别:
栈的优势是,存取速度比堆要快,仅次于直接位于CPU中的寄存器。但缺点是,存在栈中的数据大小与生存期必须是确定的,缺乏灵活性。另外,栈数据可以共享。堆的优势是可以动态地分配内存大小,生存期也不必事先告诉编译器,Java的垃圾收集器会自动收走这些不再使用的数据。但缺点是,由于要在运行时动态分配内存,存取速度较慢。

在函数中定义的一些基本类型的变量和对象的引用变量都是在函数的栈内存中分配,当在一段代码块定义一个变量时,Java 就在栈中为这个变量分配内存空间,当超过变量的作用域后,Java 会自动释放掉为该变量分配的内存空间,该内存空间可以立即被另作它用。

堆内存用来存放由 new 创建的对象和数组,在堆中分配的内存,由 Java 虚拟机的自动垃圾回收器来管理。在堆中产生了一个数组或者对象之后,还可以在栈中定义一个特殊的变量,让栈中的这个变量的取值等于数组或对象在堆内存中的 首地址,栈中的这个变量就成了数组或对象的引用变量,以后就可以在程序中使用栈中的引用变量来访问堆中的数组或者对象,引用变量就相当于是为数组或者对象 起的一个名称。引用变量是普通的变量,定义时在栈中分配,引用变量在程序运行到其作用域之外后被释放。而数组和对象本身在堆中分配,即使程序运行到使用 new 产生数组或者对象的语句所在的代码块之外,数组和对象本身占据的内存不会被释放,数组和对象在没有引用变量指向它的时候,才变为垃圾,不能在被使用,但仍 然占据内存空间不放,在随后的一个不确定的时间被垃圾回收器收走(释放掉)。

3. java中变量在内存中的分配

(1)类变量(static修饰的变量):在程序加载时系统就为它在堆中开辟了内存,堆中的内存地址存放于栈以便于高速访问。静态变量的生命周期–一直持续到整个”系统”关闭

(2)实例变量:当你使用java关键字new的时候,系统在堆中开辟并不一定是连续的空间分配给变量(比如说类实例),然后根据零散的堆内存地址,通过哈希算法换算为一长串数字以表征这个变量在堆中的”物理位置”。 实例变量的生命周期–当实例变量的引用丢失后,将被GC(垃圾回收器)列入可回收“名单”中,但并不是马上就释放堆中内存

(3)局部变量:局部变量,由声明在某方法,或某代码段里(比如for循环),执行到它的时候在栈中开辟内存,当局部变量一但脱离作用域,内存立即释放

4. JVM内存分配
JVM 将内存区域划分为: Method Are(Non-Heap)(方法区),Heap(堆),Program Counter Register(程序计数器),VM Stack(虚拟机栈,也有翻译成JAVA 方法栈的),Native Method Stack(本地方法栈)。
方法区和堆是线程共享的,虚拟机栈,程序计数器和本地方法栈是非线程共享的。
一般性的 Java 程序的工作过程:一个 Java 源程序文件,会被编译为字节码文件(以 class 为扩展名),每个java程序都需要运行在自己的JVM上,然后告知 JVM 程序的运行入口,再被 JVM 通过字节码解释器加载运行。那么程序开始运行后,都是如何涉及到各内存区域的呢?
概括地说来,JVM初始运行的时候都会分配好方法区和堆,而JVM每遇到一个线程,就为其分配一个程序计数器,虚拟机栈和本地方法栈,当线程终止时,三者(虚拟机栈,本地方法栈和程序计数器)所占用的内存空间也会被释放掉。这也是为什么我把内存区域分为线程共享和非线程共享的原因,非线程共享的那三个区域的生命周期与所属线程相同,而线程共享的区域与JAVA程序运行的生命周期相同,所以这也是系统垃圾回收的场所只发生在线程共享的区域(实际上对大部分虚拟机来说知发生在Heap上)的原因。

5. Java实现多线程,创建并启动线程的过程

(1)定义线程:
1)扩展java.lang.Thread类。 2)实现java.lang.Runnable接口。

(2)实例化线程:
1)如果是扩展java.lang.Thread类的线程,则直接new即可。
2)如果是实现了java.lang.Runnable接口的类,则用Thread的构造方法:
Thread(Runnable target)
Thread(Runnable target, String name)
Thread(ThreadGroup group, Runnable target)
Thread(ThreadGroup group, Runnable target, String name)
Thread(ThreadGroup group, Runnable target, String name, long stackSize)

(3)启动线程:在线程的Thread对象上调用start()方法,而不是run()或者别的方法。

通过继承Thread类创建线程:
public class ThreadDemo02 extends Thread{
public void run(){
System.out.println(“线程启动!”);
}
public static void main(String[] args) {
ThreadDemo02 thread = new ThreadDemo02();
thread.start();
}
}
通过实现Runnable接口创建线程:
public class ThreadDemo03 implements Runnable{
public void run() {
System.out.println(“线程启动02”);
}
public static void main(String[] args) {
Thread thread01 = new Thread(new ThreadDemo03());
thread01.start();
}
}
start()方法和run()方法的区别:
(1)启动一个线程是start()方法,启动线程之后start()方法会去调用run方法内容。
(2)start是创建并启动一个线程,而run是要运行线程中的代码。
(3)run()方法 : 在本线程内调用该Runnable对象的run()方法,可以重复多次调用;
start()方法 : 启动一个线程,调用该Runnable对象的run()方法,不能多次启动一个线程;

7. 在JAVA中,有六个不同的地方可以存储数据

(1) 寄存器(register):这是最快的存储区,因为它位于不同于其他存储区的地方——处理器内部。但是寄存器的数量极其有限,所以寄存器由编译器根据需求进行分配。你不能直接控制,也不能在程序中感觉到寄存器存在的任何迹象。
(2) 堆栈(stack):位于通用RAM中,但通过它的“堆栈指针”可以从处理器哪里获得支持。堆栈指针若向下移动,则分配新的内存;若向上移动,则释放那些内存。这是一种快速有效的分配存储方法,仅次于寄存器。创建程序时候,JAVA编译器必须知道存储在堆栈内所有数据的确切大小和生命周期,因为它必须生成相应的代码,以便上下移动堆栈指针。这一约束限制了程序的灵活性,所以虽然某些JAVA数据存储在堆栈中——特别是对象引用,但是JAVA对象不存储其中。
(3)堆(heap):一种通用性的内存池(也存在于RAM中),用于存放所以的JAVA对象。堆不同于堆栈的好处是:编译器不需要知道要从堆里分配多少存储区域,也不必知道存储的数据在堆里存活多长时间。因此,在堆里分配存储有很大的灵活性。当你需要创建一个对象的时候,只需要new写一行简单的代码,当执行这行代码时,会自动在堆里进行存储分配。当然,为这种灵活性必须要付出相应的代码。用堆进行存储分配比用堆栈进行存储存储需要更多的时间。
(4)静态存储(static storage):这里的“静态”是指“在固定的位置”。静态存储里存放程序运行时一直存在的数据。你可用关键字static来标识一个对象的特定元素是静态的,但JAVA对象本身从来不会存放在静态存储空间里。
(5)常量存储(constant storage):常量值通常直接存放在程序代码内部,这样做是安全的,因为它们永远不会被改变。有时,在嵌入式系统中,常量本身会和其他部分分割离开,所以在这种情况下,可以选择将其放在ROM中
(6)非RAM存储:如果数据完全存活于程序之外,那么它可以不受程序的任何控制,在程序没有运行时也可以存在。

8. java concurrent包下的4个类,选出差别最大的一个 (C)
A. Semaphore B. ReentrantLock C. Future D. CountDownLatch
别的类都处理线程间的关系,处理并发机制,但Future 只用于获取线程结果。
Future是个接口,表示获取一个正在指定的线程的结果。对该线程有取消和判断是否执行完毕等操作。
CountDownLatch 是个锁存器,他表示我要占用给定的多少个线程且我优先执行,我执行完之前其他要使用该资源的都要等待。
Semaphore,就像是一个许可证发放者,也像一个数据库连接池。证就这么多,如果池中的证没换回来,其他人就不能用。
ReentrantLock 和 synchronized一样,用于锁定线程。

猜你喜欢

转载自blog.csdn.net/jianyuerensheng/article/details/53155974
今日推荐