jvm做了什么?(68h)158

2024-02-27 06:10
文章标签 java jvm 158 68h

本文主要是介绍jvm做了什么?(68h)158,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

one.内存与垃圾回收

一.JVM的整体结构(主要针对于HotSpot)

常见的虚拟机:HotSpot, J9, JRockit

 二.虚拟机的生命周期

a.启动->Java虚拟机的启动是通过引导类加载器创建一个初始类来完成的, 这个类由虚拟机的具体实现来指定;

b.执行->

一个运行中的Java虚拟机有着一个清晰的任务:执行Java程序.

所以, Java虚拟机在Java程序开始执行时才会运行, 在Java程序结束时就会停止.

然而, 执行一个所谓的Java程序的时候, 真正执行的是一个Java虚拟机的进程.

c.退出->

程序正常执行结束时;

程序执行过程中遇到了异常或错误而异常终止;

调用System类的exit方法等;

三.类的加载

1.类的加载过程:

 加载:

        a.通过一个类的全限定名获取定义此类的二进制字节流;        

        b.将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构;

        c.在内存中生成一个代表这个类的java.lang.Class对象, 作为方法区这个类的各种数据的访问接口.

验证:

        主要为了确保class文件的字节流中包含的信息是否符合当前的虚拟机要求,为了让虚拟机不受危害.        

        .class文件的字节流以 "CA FE BA BE"开头

准备:

        a.为类变量分配内存并且设置该变量的默认初始化值;

        b.这里不包括static final 修饰的变量, 因为这种变量在编译阶段就会被分配初始化值, 而在准备阶段会直接进行显式初始化;

        c.这里不会为实例变量分配初始化, 类变量会分配在方法区中, 而实例变量会随着对象一起分配到java堆中

初始化:

        a.初始化阶段就是执行类构造器方法<clinit>()的过程;

        b.此方法不需要定义, 它是javac编译器自动收集类中所有类变量的赋值动作和静态代码块中的语句合并而来;

        c.构造器方法中的指令按照语句在源文件中出现的顺序执行;

        d.<clinit>()不同于类的构造器.(构造器是虚拟机视角下的 <init>());

        e.若该类具有父类, jvm会保证子类的<clinit>()执行前, 父类的<clinit>()已经执行完毕;

        f.虚拟机必须保证一个类的<clinit>()方法在多线程下被同步加锁执行.

2.类的加载器

ClassLoader类, 它是一个抽象类, 除了引导类加载器以外都继承于它.

        加载规则:

                引导类加载器: 负责加载java的核心类库;

                扩展类加载器: 加载包java/lib/ext下的类;

                应用类加载器:

        双亲委派机制:

                jvm对class文件采用的是按需加载, 也就是说当需要使用该类时才会将它的class文件加载到内存生成Class对象, 而且在加载类的class文件时, jvm采用的是双亲委派机制.

                工作原理:

                        a.如果一个类加载器收到了类加载请求, 它并不会先去加载, 而是把这个请求委托给父类的加载器去执行;

                        b.如果父类加载器还存在其父类加载器, 则进一步向上委托, 依次递归, 加载请求最终将到达顶层的启动类加载器;

                        c.如果父类加载器可以完成类加载任务, 就返回成功, 倘若父加载器无法完成此加载任务, 子加载器才会尝试去加载, 这就是双亲委派机制. 

                双亲委派机制的作用:

                        a.避免类的重复加载;

                        b.保护程序安全, 防止核心API被恶意篡改;

        沙箱安全机制:

                自定义一个java.lang.String类并写一个main()方法并运行, 但是在加载自定义类String的时候会率先使用引导类加载器进行加载, 而引导类加载器在加载的过程中会先加载jdk自带的String,就会报错说没有找到main()方法, 这样可以保证对java核心API的保护, 这就是沙箱安全机制.

3.java程序对类的使用方式:

        a.主动使用,分为七种方式:

                A.创建类的实例;

                B.访问某个类或接口的静态变量, 或者对静态变量进行赋值;

                C.调用类的静态方法;

                D. 反射;

                E.初始化一个类的子类;

                F.jvm启动时被表名启动类的类;

                G.JDK 7开始提供的动态语言支持:

                        java.lang.invoke.MethodHandle实例的解析结果;

                        REF_getStatic、REF_putStatic、REF_invokeStatic句柄对应的类没有初始化则进行初始化;

        b.被动使用

                除了以上七种情况, 其他使用java类的方式都可以被看作是对类的被动使用, 类的被动使用不会进行类的初始化(类的加载过程中的初始化,会调用<clinit>()方法)

:

1.ClassLoader只负责.class文件的加载, 至于它是否可以运行, 则由Execution Engine决定.

2.引导类加载器:是c和c++语言编写的,其他的加载器都是使用java实现的

四.运行时数据区

内存是硬盘和CPU的中间仓库和桥梁, 

1.程序计数器(线程私有)

1.介绍和作用:pc寄存器用来存储指向一条指令的地址, 即将要执行的指令代码. 由执行引擎读取下一条指令.

 2.为什么使用pc寄存器记录当前线程的执行地址?

        因为CPU需要不停地切换各个线程, 当再次切换会当前线程后, 就可以通过线程私有的pc获取到接下来从哪儿开始继续执行;

 .

2.虚拟机栈(线程私有)

由于跨平台的设计, java的指令都是根据栈来设计的.不同平台的CPU架构不同, 所以不能设计为基于寄存器的.

栈内部保存着一个个栈帧,每个方法对应一个栈帧.

作用:

        主管java程序的运行, 用于保存方法的局部变量、部分结果, 并参与方法的调用和返回.

调整栈内存的大小:

        -Xss500m  -->>  给栈内存的大小设置为500m

栈的存储单位:

        a.每个线程都有自己的栈, 栈中的数据都是以栈帧为基本单位进行存储.

                栈帧是一个内存区块, 是一个数据集, 维系着方法执行过程中的各种数据信息.

每个栈帧中存储什么?

         a.局部变量表(Local Variables);

        b.操作数栈(Operand Stack) ,或叫表达式栈;

        c.动态链接(Dynamic Linking), 或叫指向运行时常量池的方法引用;

        d.方法返回地址(Return Address), 或叫方法正常退出或异常退出的定义;

        e.一些附加信息;

1.局部变量表(采用数组结构)  -->>  LV

        a.定义为一个数字数组, 主要用于存储方法参数和定义在方法内的局部变量,这些数据类型包括基本数据类型、对象引用(reference)、以及returnAddress(返回值)类型.        

        b.局部变量表所需的容量大小是在编译期就确定下来的,并保存在方法的Code属性的maxinum local variables数据项中.所以在方法运行期间是不会修改局部变量表的大小的.        

        c.局部变量表中的变量也是重要的垃圾回收的根节点, 只要被局部变量表中直接或间接引用的对象都不会被垃圾回收.

关于对Slot(槽)的理解:

        a.它是局部变量表最基本的存储单元;

        b.在局部变量表里, 32为以内的类型只占用一个slot(包括returnAddress类型和引用数据类型), 64位的类型(long和double)占用2个slot.

        c.在存储前, byte、short、char被转换为int, boolean也被转换为int类型, 0表示false, 非0表示true.        

        d.如果当前帧是由构造方法或实例方法创建的, 那么该对象引用的this(它也相当于一个变量)将会放在局部变量表index为0的slot处, 其余的局部变量按照声明顺序进行排列.

        e.局部变量表中的slot是可以重复利用的, 如果一个变量过了其作用域, 那么在其作用域之后声明的变量可能复用过期变量的slot, 从而达到节省资源的作用.

2.操作数栈(也是一种栈,采用数组结构)  -->>  OS

        在方法的执行过程中, 根据字节码指令, 往栈中写入数据(入栈)或提取数据(出栈).

        a.主要用于保存计算过程的中间结果, 同时作为计算过程中变量的临时存储空间;

        b.当一个新的栈帧被创建出来的时候, 该栈帧对应的操作数栈是空的(只是没有数据,但是数组存在);

        c.每一个操作数栈都会拥有一个明确的栈深度用于数值存储, 其所需的最大深度在编译期就定义好了, 保存在方法的Code属性中, 为max_stack的值;

        d.栈中的元素可以是任意的java数据类型:

                32bit的类型占用一个栈单位深度;

                64bit的类型占用两个栈单位深度;

        e.操作数栈里边的数据只能通过标准的入栈和出栈操作进行访问;

        f.如果被调用方法带有返回值, 其返回值将被压入当前栈帧(调用方法对应的栈帧)的操作数栈中;        

        g.栈顶缓存技术.

3.动态链接(或叫指向运行时常量池的方法引用)  -->>  DL

        a.在java源文件被编译到字节码文件中时, 所有的变量和方法引用都作为符号引用保存在class文件的常量池里, 那么动态链接的作用就是为了将这些符号引用转换为调用方法的直接引用.

        b.每一个栈帧内部都包含一个指向运行时常量池中该栈帧所属方法的引用.包含这个引用的目的就是为了支持当前方法的代码能够实现动态链接.

4.方法返回地址  -->> RA

        用于存放调用该方法的pc寄存器的值.

        无论方法是否以正常的方式退出, 在方法退出后都会返回到该方法被调用的位置, 该位置则可以通过方法返回地址来确定.

5.一些附加信息

方法返回值

        一个方法正常调用完成之后究竟需要使用哪一个返回指令还需要根据返回值的类型来决定:

                ireturn  -->>  当返回值是byte,short,int,char,boolean的时候使用该指令

                lreturn  -->>  为long时

                freturn  -->>  为float时

                dreturn  -->>  为double时

                areturn  -->>  为引用数据类型时

                return  -->>  方法为void时, 还有方法为<init>和<clinit>()时

方法的调用:

        非虚方法:

                如果方法在编译期间就确定了具体的调用版本, 那么这个版本在运行时是不可变的.

                静态方法,私有方法,final方法,实例构造器,父类方法都是非虚方法.

        除了以上这些,其余都是虚方法.

        虚拟机中提供一下几条调用指令:

                a. invokestatic  -->>  调用静态方法,解析阶段确定唯一方法版本

                b. invokespecial  -->>  调用<init>方法,私有及父类方法,解析阶段确定唯一方法版本.

                c. invokevirtual  -->>  调用所有虚方法

                d. invokeinterface  -->>  调用接口方法

                以上四个指令中, invokestatic和invokespecial 调用的是非虚方法,invokevirtual和invokeinterface则调用的是虚方法(如果调用的是final的方法,也是使用invokevirtual).

        

注:

1.栈是运行时的单位, 而堆是存储的单位.

        栈解决程序的运行问题, 即程序如何运行, 或者说如何处理数据. 栈解决的是数据存储的问题,即数据怎么放、放在哪儿.

2.jvm对虚拟机栈的操作只有两个, 就是压栈和出栈.所以在一条活动线程中, 一个时间点上, 只会有一个活动的栈帧, 即当前正在执行的方法的栈帧(栈顶栈帧)是有效的, 这个栈帧被称为当前栈帧, 当前栈帧对应的方法叫做当前方法, 定义这个方法的类就是当前类.所以执行引擎运行的所有字节码指令只针对当前栈帧进行操作.

3.不同线程中所包含的栈帧是不能相互引用的, 即不可能在一个栈帧中引用另外一个线程的栈帧.

4.如果当前方法调用了其他方法, 方法返回之际, 当前栈帧会传回此方法的返回结果给前一个栈帧, 接着, 虚拟机会丢弃当前栈帧, 使得前一个栈帧成为当前栈帧.

5.java方法有两种返回函数的方式:一种是正常的函数返回, 使用return指令; 另一种是抛出异常, 两种方式都会导致栈帧被弹出.

3.堆(线程共享)

jdk7及以前逻辑上分为:新生代,老年代,永久代

jdk8及以后逻辑上分为:新生代,老年代,元空间

1.关于TLAB

1.从内存模型而不是垃圾回收的角度对伊甸园区进行划分, jvm为每个线程分配了一个私有缓存区域, 它包含在伊甸园区内, 默认情况下, TLAB仅占伊甸园空间的1/100, 默认开启.

2.一旦对象在TLAB空间分配内存失败时, jvm就会尝试着通过使用加锁机制确保数据操作的原子性, 从而直接在伊甸园区分配内存.

3.TLAB内存分配过程:

2.我们new 的对象只会存在于堆中吗?  -->>  逃逸分析(默认开启)

是.

当一个对象a在方法m中被定义后, 该对象如果只会在方法m中被使用到, 则认为该对象没有发生逃逸.

1.使用逃逸分析, 我们可以对代码进行优化:

        a.栈上分配:对象如果永不逃逸, 则在栈上给对象分配内存是候选, 而不是堆分配;

        b.同步省略:如果一个对象被发现只会被一个线程访问, 那么对于该对象的操作可以不考虑同步;

        c.分离对象或标量替换(jvm正在应用);

垃圾回收过程图示

 

注:

1.java虚拟机规范规定, 堆可以处于物理上不连续的内存空间中, 但在逻辑上它应该被视为连续的.

2.堆还可以划分现成私有的缓冲区(Thread Local Allocation Buffer, TLAB).

3.堆空间默认大小:初始内存(物理内存的1/64),最大内存(物理内存的1/4).

4.不是所有对象都是在伊甸园区new出来的.

5.伊甸园区与幸存者区的大小比例默认8:2, 但实际上只有6:2.

6.如果幸存者区中相同年龄的所有对象大小的总和大于幸存者空间的一半, 则年龄大于或等于该年龄的对象可以直接步入老年代而无需等到15岁.

7.jvm空间分配担保策略:

        在Minor GC之前, 虚拟机会检查得知老年代最大可用的连续空间大于新生代所有对象的总空间或者历次晋升的平均空间大小就直接进行Minor GC, 否则将进行Full GC.

4.方法区/永久代/元空间(线程共享)

1.栈, 堆, 方法区的交互关系

 2.元空间与永久代不同, 如果不指定大小, 默认情况下, 虚拟机会耗尽所有的可用系统内存.所以我们可以设置初始的元空间大小.对于一个64位的服务器端jvm来说, 其默认值是21MB.这就是初始的高水位线, 一旦触及这个水位线, Full GC就会被触发并卸载没用的类(即这些类对应的类加载器不再存活), 然后这个高水位线将会重置.新的高水位线的值取决于GC后释放了多少空间, 如果释放空间后不是很充足, 那么会适当提高该值.如果释放空间过多, 则会适当降低该值.

3.方法区的变化

注:

1.方法区又叫永久代(jdk1.7)和元空间(jdk1.8), 永久代使用的是jvm的内存,元空间使用的是本地内存.

2.要想分析OOM, 我们首先要导出来各个内存使用情况的快照(我们可以通过jVisual得到这个文件), 我们可以称之为dump文件, 然后通过JVisual等工具导入此dump文件, 通过堆dump文件的分析推论出到底是出现了内存泄漏(简单理解就是有些对象不用了但是还有指针指向它)还是内存溢出(OOM).

3.方法区用于存储已被虚拟机加载的类信息、常量、静态变量、即时编辑器编译后的代码缓存等.

类信息:

        a.关于类和字段的:

                A.这个类的完全有效名称(包名+类名);

                B.这个类的直接父类的完全有效名称;

                C.这个类的修饰符;

                D.这个类的直接接口的完全有效名称;

                E.域的修饰符、域的类型、域的名称;

        b.关于方法的:

                A.方法修饰符、方法返回值类型、方法名称、方法参数的类型和名称;

                B.方法的字节码

                C.操作数栈和局部变量表的大小

                D.异常表->每个异常处理的开始/结束位置,被捕获的异常类的常量池索引,代码处理在PC中的偏移地址.        

        c.,即时编译器编译后的代码缓存

4.字节码的常量池被加载到方法区以后叫做运行时常量池.

五.对象的实例化、内存布局和访问定位

1.对象实例化大致过程:

        类的加载 -> 为对象分配内存 -> 初始化属性 -> 设置对象头 -> <init>()

        a.判断对象对应的类是否加载、链接、初始化

        b.为对象分配内存

                如果内存规整  -->>  指针碰撞

                如果不规整  -->>  虚拟机需要维护一个空闲列表(列表记录哪些内存没有被占用)        

        c.处理并发安全问题

                采用cas失败重试、区域加锁保证更新的原子性

                每个线程预先分配一块TLAB

        d.初始化分配到的空间

                所有属性设置默认初始化值, 保证对象实例字段在不赋值的前提下也能使用

        e.设置对象头

        f.执行<init>()方法进行初始化

2.内存布局:

        a.对象头

                A.哈希值 -> 对象所处的内存位置

                B.GC分代年龄

                C.锁状态标志

                D.持有锁的线程

                E.偏向线程ID

                F.偏向时间戳

                G.类型指针 -> 指向类元数据, 确定该对象所属的类型

                如果是数组, 还需记录数组的长度.

        b.实例数据

        c.对齐填充

3.如何访问定位

        a.句柄访问

         b.直接指针 

指针碰撞:

如果内存是规整的, 那么虚拟机将采用的是指针碰撞法来为对象分配内存.

意思是所有用过的内存都在一边, 空闲的内存在另一边, 中间放着一个指针作为分界点. 分配内存就仅仅是把指针往空闲的那边挪动一段与对象大小相等的距离罢了.

六.执行引擎

任务: 将字节码指令解释/编译为对应平台上的本地机器指令

1.工作过程:

        a.执行引擎在执行的过程中究竟需要执行什么字节码指令完全依赖于PC;

        b.每当执行完一条指令操作后, PC寄存器就会更新为下一条需要被执行的指令地址;

        c.方法在执行的过程中, 执行引擎有可能会通过存储在局部变量表中的对象引用准确定位到存储在java堆中的对象实例信息, 以及通过对象头中的类型指针定位到当前对象的类型信息.

2.解释器和JIT编译器

        a.解释器:当java虚拟机启动时会根据预定义的规范对字节码采用逐行解释的方式执行,将每条字节码文件中的内容翻译为对应平台的本地机器指令执行.        

        b.JIT编译器:就是虚拟机将源代码直接编译成和本地机器语言.

七.StringTable

String被声明为final的, 所以不可以被继承.jdk1.8底层使用char型数组存储.1.9以后使用byte数组存储.

StringTable存在垃圾回收行为.

1.String pool的基本特性:

        String pool是一个固定长度的HashTable, 默认长度为60013(jdk1.7), 使用HashTable的好处是字符串常量池中不会存储相同内容的字符串.

2.字符串拼接操作:

        a.常量与常量的拼接结果在常量池, 原理是编译期会直接进行优化;        

        b.常量池中不会存在相同内容的常量;

        c.拼接的过程中只要有一个是变量(被final修饰的是常量), 结果就在堆中.此变量拼接的原理是StringBuilder;

        d.如果拼接的结果调用了intern()方法, 则主动将常量池中还没有的字符串对象放入池中, 并返回此对象地址;

3.关于intern()方法:(jvm视频127)

调用intern()方法可以让jvm主动在字符串常量池中放入一个字符串常量.

八.垃圾回收

jdk中默认的垃圾回收器:G1

8.1.垃圾回收相关算法

标记阶段:

1.引用计数法:

2.可达性分析:

        a.可达性分析算法以根对象集合(GC Roots)为起点, 然后判断某个对象是否能够被GC Roots所直接或间接的连接.

        b.使用可达性分析算法后, 内存中存活的对象都会被GC Roots中的对象直接或间接的连接着, 这个连接称为引用链.

清除阶段:

1.标记清除算法(Mark sweep)

        执行过程:

                标记:Collector从引用根节点进行遍历, 标记所有被引用的对象, 在对象的对象头中进行记录.

                清除:Collector对堆内存从头到尾进行线性遍历, 如果发现某个对象在其对象头中并没有被标记为可达对象, 则将其回收.

        缺点:这种方式清理出来的内存是不连续的, 会产生内存碎片.所以还需要维护一个空闲列表.

        何为清除?

                清除并不是把不可达的对象直接置空, 而是把需要清除的对象地址保存在空闲的地址列表里. 下次有新对象需要加载时, 判断垃圾的内存空间是否足够, 如果足够直接存放.

2.复制算法

        from区和to区使用的算法.

3.标记压缩算法(Mark Compact)

        执行过程:

                标记:从根节点开始标记所有被引用的对象

                压缩:将所有的存活对象压缩到内存的一端, 按顺序存放.然后清理边界外所有的空间. 

GC Roots包含哪些元素?

        a.虚拟机栈中引用的对象;

        b.本地方法栈中引用的对象;

        c.方法区中类静态变量所引用的对象;

        d.方法区中常量所引用的对象;

        e.所有被同步锁synchronized持有的对象;

        f.java虚拟机内部的引用.

对象的finalization机制:

        在垃圾回收之前, 总是会先调用该对象的finalize()方法.此方法可以用于在对象被回收时进行资源释放,比如关闭文件、套接字、数据库连接等.

        由于finalize()方法的存在, 虚拟机中的对象一般处于三种可能得状态:

                a.可触及的:从根节点开始, 可以到达这个对象

                b.可复活的:对象的所有"被引用"都被释放, 但是对象可能在finalize()中复活

                c.不可触及的:如果对象的finalize()方法被调用, 而且当前对象没有复活,那么就会进入不可触及状态.不可触及的对象不可能复活, 因为finalize()方法只可能被调用一次.

MAT

它是Memory Analyzer的简称, 它是一款强大的java堆内存分析工具,用于查找内存泄漏和查看内存消耗情况.

分代收集算法:每个不同的内存区域结合自身的特点采用不同的垃圾回收算法.

增量收集算法:垃圾收集线程只收集一小片的内存区域(尽量少的stop the world), 接着切换至用户线程.

分区算法:基本原理和增量收集算法差不多.

8.2.垃圾回收相关概念

1.System.gc()

        如果上述代码被调用, 会显式触发Full GC, 它只是提醒jvm进行GC, 具体什么时候GC不确定.

        System.runFinalization()  -->> 如果在System.gc()以后调用了该代码, 则会强制调用失去引用的对象的finalize()方法,也就是强制进行gc.

2.内存溢出和内存泄漏

        内存泄漏: 严格来说, 只有对象不会被程序所使用, 但gc又不能回收的情况才可以叫内存泄漏.

two.字节码与类的加载

three.性能监控与调优

four.面试总结,

这篇关于jvm做了什么?(68h)158的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/751434

相关文章

JVM 的类初始化机制

前言 当你在 Java 程序中new对象时,有没有考虑过 JVM 是如何把静态的字节码(byte code)转化为运行时对象的呢,这个问题看似简单,但清楚的同学相信也不会太多,这篇文章首先介绍 JVM 类初始化的机制,然后给出几个易出错的实例来分析,帮助大家更好理解这个知识点。 JVM 将字节码转化为运行时对象分为三个阶段,分别是:loading 、Linking、initialization

Spring Security 基于表达式的权限控制

前言 spring security 3.0已经可以使用spring el表达式来控制授权,允许在表达式中使用复杂的布尔逻辑来控制访问的权限。 常见的表达式 Spring Security可用表达式对象的基类是SecurityExpressionRoot。 表达式描述hasRole([role])用户拥有制定的角色时返回true (Spring security默认会带有ROLE_前缀),去

浅析Spring Security认证过程

类图 为了方便理解Spring Security认证流程,特意画了如下的类图,包含相关的核心认证类 概述 核心验证器 AuthenticationManager 该对象提供了认证方法的入口,接收一个Authentiaton对象作为参数; public interface AuthenticationManager {Authentication authenticate(Authenti

Spring Security--Architecture Overview

1 核心组件 这一节主要介绍一些在Spring Security中常见且核心的Java类,它们之间的依赖,构建起了整个框架。想要理解整个架构,最起码得对这些类眼熟。 1.1 SecurityContextHolder SecurityContextHolder用于存储安全上下文(security context)的信息。当前操作的用户是谁,该用户是否已经被认证,他拥有哪些角色权限…这些都被保

Spring Security基于数据库验证流程详解

Spring Security 校验流程图 相关解释说明(认真看哦) AbstractAuthenticationProcessingFilter 抽象类 /*** 调用 #requiresAuthentication(HttpServletRequest, HttpServletResponse) 决定是否需要进行验证操作。* 如果需要验证,则会调用 #attemptAuthentica

Spring Security 从入门到进阶系列教程

Spring Security 入门系列 《保护 Web 应用的安全》 《Spring-Security-入门(一):登录与退出》 《Spring-Security-入门(二):基于数据库验证》 《Spring-Security-入门(三):密码加密》 《Spring-Security-入门(四):自定义-Filter》 《Spring-Security-入门(五):在 Sprin

Java架构师知识体认识

源码分析 常用设计模式 Proxy代理模式Factory工厂模式Singleton单例模式Delegate委派模式Strategy策略模式Prototype原型模式Template模板模式 Spring5 beans 接口实例化代理Bean操作 Context Ioc容器设计原理及高级特性Aop设计原理Factorybean与Beanfactory Transaction 声明式事物

Java进阶13讲__第12讲_1/2

多线程、线程池 1.  线程概念 1.1  什么是线程 1.2  线程的好处 2.   创建线程的三种方式 注意事项 2.1  继承Thread类 2.1.1 认识  2.1.2  编码实现  package cn.hdc.oop10.Thread;import org.slf4j.Logger;import org.slf4j.LoggerFactory

JAVA智听未来一站式有声阅读平台听书系统小程序源码

智听未来,一站式有声阅读平台听书系统 🌟&nbsp;开篇:遇见未来,从“智听”开始 在这个快节奏的时代,你是否渴望在忙碌的间隙,找到一片属于自己的宁静角落?是否梦想着能随时随地,沉浸在知识的海洋,或是故事的奇幻世界里?今天,就让我带你一起探索“智听未来”——这一站式有声阅读平台听书系统,它正悄悄改变着我们的阅读方式,让未来触手可及! 📚&nbsp;第一站:海量资源,应有尽有 走进“智听

在cscode中通过maven创建java项目

在cscode中创建java项目 可以通过博客完成maven的导入 建立maven项目 使用快捷键 Ctrl + Shift + P 建立一个 Maven 项目 1 Ctrl + Shift + P 打开输入框2 输入 "> java create"3 选择 maven4 选择 No Archetype5 输入 域名6 输入项目名称7 建立一个文件目录存放项目,文件名一般为项目名8 确定