https://blog.csdn.net/weixin_41382054/article/details/107964417
hibernate是一個開源框架,它是物件關聯關係對映的框架,它對JDBC做了輕量級的封裝,而我們java程式設計師可以使用物件導向的思想來操縱數據庫。
hibernate核心介面
session:負責被持久化物件CRUD操作
sessionFactory:負責初始化hibernate,建立session物件
configuration:負責設定並啓動hibernate,建立SessionFactory
Transaction:負責事物相關的操作
Query和Criteria介面:負責執行各種數據庫查詢
hibernate對JDBC存取數據庫的程式碼做了封裝,大大簡化了數據存取層繁瑣的重複性程式碼。
hibernate是一個基於JDBC的主流持久化框架,是一個優秀的ORM實現。他很大程度的簡化DAO層的編碼工作。
hibernate使用Java反射機制 機製,而不是位元組碼增強程式來實現透明性。
hibernate的效能非常好,因爲它是個開放原始碼的物件關係對映(ORM)框架。對映的靈活性很出色。它支援各種關係數據庫,從一對一到多對多的各種複雜關係。
可以很方便的進行數據庫的移植工作。
提供了快取機制 機製,是程式執行更改的高效。
Hibernate中怎樣實現類之間的關係?(如:一對多、多對多的關係)
類與類之間的關係主要體現在表與表之間的關係進行操作,它們都是對物件進行操作,我們程式中把所有的表與類都對映在一起,它們通過組態檔中的many-to-one、one-to-many、many-to-many、
物件-關係對映(Object-Relational Mapping,簡稱ORM),物件導向的開發方法是當今企業級應用開發環境中的主流開發方法,關係數據庫是企業級應用環境中永久存放數據的主流數據儲存系統。物件和關係數據是業務實體的兩種表現形式,業務實體在記憶體中表現爲物件,在數據庫中表現爲關係數據。記憶體中的物件之間存在關聯和繼承關係,而在數據庫中,關係數據無法直接表達多對多關聯和繼承關係。因此,物件-關係對映(ORM)系統一般以中介軟體的形式存在,主要實現程式物件到關係數據庫數據的對映。
第二種回答:
ORM(Object Relation Mapping)物件關係對映,是把數據庫中的關係數據對映成爲程式中的物件。
使用 ORM 的優點:
提高了開發效率降低了開發成本、開發更簡單更物件化、可移植更強。
在 Config 裏面把 hibernate. show_SQL 設定爲 true 就可以。但不建議開啓,開啓之後會降低程式的執行效率。
注意:
每個編譯工具的設定稍微有點區別,具體看你使用哪種編譯工具,再具體去設定。
<property name="hibernateProperties">
<value>
hibernate.dialect=org.hibernate.dialect.Oracle9Dialect
hibernate.show_sql=true
</value>
</property>
拓展:以MyEclipse中Hibernate的demo爲例,在hibernate.cfg.xml檔案中設定如下:
在這裏插入圖片描述
參考詳細程式碼:
<?xml version='1.0' encoding='UTF-8'?>
<!DOCTYPE hibernate-configuration PUBLIC
"-//Hibernate/Hibernate Configuration DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd">
<!-- Generated by MyEclipse Hibernate Tools. -->
<hibernate-configuration>
<!-- 一般一個session-factory節點就對應一個數據庫 testdemo-->
<session-factory>
<!-- 數據庫連線參數 -->
<property name="dialect">
org.hibernate.dialect.MySQLDialect
</property>
<property name="connection.url">
jdbc:mysql://127.0.0.1:3306/testdemo
</property>
<property name="connection.username">root</property>
<property name="connection.password">root</property>
<property name="connection.driver_class">
com.mysql.jdbc.Driver
</property>
<property name="myeclipse.connection.profile">mysql</property>
<!-- 顯示Hibernate執行的sql -->
<property name="show_sql">true</property>
<!-- 格式化sql -->
<property name="format_sql">true</property>
<!-- 設定對映檔案 -->
<mapping resource="com/gx/po/User.hbm.xml" />
<mapping resource="com/gx/po/Role.hbm.xml" />
<mapping resource="com/gx/vo/UserVo.hbm.xml"/>
</session-factory>
</hibernate-configuration>
三種:hql、原生 SQL、條件查詢 Criteria。
hql查詢
sql查詢
條件查詢
hql查詢,sql查詢,條件查詢
HQL: Hibernate Query Language. 物件導向的寫法:
Query query = session.createQuery("from Customer where name = ?");
query.setParameter(0, "張三");
Query.list();
SQL:
SQLQuery query = session.createSQLQuery("select * from customer");
List<Object[]> list = query.list();
SQLQuery query = session.createSQLQuery("select * from customer");
query.addEntity(Customer.class);
List<Customer> list = query.list();
QBC: Query By Criteria.(條件查詢)
Criteria criteria = session.createCriteria(Customer.class);
criteria.add(Restrictions.eq("name", "李四"));
List<Customer> list = criteria.list();
Hibernate的查詢方式
Sql、Criteria,object comptosition
Hql:
1、 屬性查詢
2、 參數查詢、命名參數查詢
3、 關聯查詢
4、 分頁查詢
5、 統計函數
HQL和SQL的區別:
HQL是物件導向查詢操作的,SQL是結構化查詢語言,是面向數據庫表結構的。
如何優化Hibernate?
1.使用雙向一對多關聯,不使用單向一對多
2.靈活使用單向一對多關聯
3.不用一對一,用多對一取代
4.設定物件快取,不使用集合快取
5.一對多集合使用Bag,多對多集合使用Set
6.繼承類使用顯式多型
7.表欄位要少,表關聯不要怕多,有二級快取撐腰
可以將Hibernate的實體類定義爲final類,但這種做法並不好。因爲Hibernate會使用代理模式在延遲關聯的情況下提高效能,如果你把實體類定義成final類之後,因爲 Java不允許對final類進行擴充套件,所以Hibernate就無法再使用代理了,如此一來就限制了使用可以提升效能的手段。不過,如果你的持久化類實現了一個介面,而且在該介面中宣告瞭所有定義於實體類中的所有public的方法的話,你就能夠避免出現前面所說的不利後果。
第二種回答:
實體類可以定義爲 final 類,但這樣的話就不能使用 hibernate 代理模式下的延遲關聯提供效能了,所以不建議定義實體類爲 final。
在Hibernate中,如果將OID定義爲Integer型別,那麼Hibernate就可以根據其值是否爲null而判斷一個物件是否是臨時的,如果將OID定義爲了int型別,還需要在hbm對映檔案中設定其unsaved-value屬性爲0。
第二種回答:
Integer 型別爲物件,它的值允許爲 null,而 int 屬於基礎數據型別,值不能爲 null。
hibernate工作原理:
1.通過Configuration config = new Configuration().configure();//讀取並解析hibernate.cfg.xml組態檔
2.由hibernate.cfg.xml中的讀取並解析對映資訊
3.通過SessionFactory sf = config.buildSessionFactory();//建立SessionFactory
4.Session session = sf.openSession();//開啓Sesssion
5.Transaction tx = session.beginTransaction();//建立並啓動事務Transation
6.persistent operate操作數據,持久化操作
7.tx.commit();//提交事務
8.關閉Session
9.關閉SesstionFactory
第二種回答:
讀取並解析組態檔。
讀取並解析對映檔案,建立 SessionFactory。
開啓 Session。
建立事務。
進行持久化操作。
提交事務。
關閉 Session。
關閉 SessionFactory。
get() 沒有使用物件的其他屬性的時候,也生成了SQL 立即載入
load() 沒有使用物件的其他屬性的時候,沒有SQL 延遲載入
詳解:
對於Hibernate get方法,Hibernate會確認一下該id對應的數據是否存在,首先在session快取中查詢,然後在二級快取中查詢,還沒有就查詢數據庫,數據 庫中沒有就返回null。
Hibernate load方法載入實體物件的時候,根據對映檔案上類級別的lazy屬性的設定(預設爲true),分情況討論:
(1)若爲true,則首先在Session快取中查詢,看看該id對應的物件是否存在,不存在則使用延遲載入,返回實體的代理類物件(該代理類爲實體類的子類,由CGLIB動態生成)。等到具體使用該物件(除獲取OID以外)的時候,再查詢二級快取和數據庫,若仍沒發現符合條件的記錄,則會拋出一個ObjectNotFoundException。
(2)若爲false,就跟Hibernateget方法查詢順序一樣,只是最終若沒發現符合條件的記錄,則會拋出一個ObjectNotFoundException。
這裏get和load有兩個重要區別:
如果未能發現符合條件的記錄,Hibernate get方法返回null,而load方法會拋出一個ObjectNotFoundException。
load方法可返回沒有載入實體數據的代理類範例,而get方法永遠返回有實體數據的物件。
總之對於get和load的根本區別,一句話,hibernate對於 load方法認爲該數據在數據庫中一定存在,可以放心的使用代理來延遲載入,如果在使用過程中發現了問題,只能拋異常;而對於get方法,hibernate一定要獲取到真實的數據,否則返回null。
第二種回答:
數據查詢時,沒有 OID 指定的物件,get() 返回 null;load() 返回一個代理物件。
load()支援延遲載入;get() 不支援延遲載入。
Hibernate快取的作用:
Hibernate是一個持久層框架,經常存取物理數據庫,爲了降低應用程式對物理數據源存取的頻次,從而提高應用程式的執行效能。快取內的數據是對物理數據源中的數據的複製,應用程式在執行時從快取讀寫數據,在特定的時刻或事件會同步快取和物理數據源的數據
Hibernate快取分類:
Hibernate快取包括兩大類:Hibernate一級快取和Hibernate二級快取。
Hibernate一級快取又稱爲「Session的快取」,它是內建的,意思就是說,只要你使用hibernate就必須使用session快取。由於Session物件的生命週期通常對應一個數據庫事務或者一個應用事務,因此它的快取是事務範圍的快取。在第一級快取中,持久化類的每個範例都具有唯一的OID。
Hibernate二級快取又稱爲「SessionFactory的快取」,由於SessionFactory物件的生命週期和應用程式的整個過程對應,因此Hibernate二級快取是進程範圍或者叢集範圍的快取,有可能出現併發問題,因此需要採用適當的併發存取策略,該策略爲被快取的數據提供了事務隔離級別。第二級快取是可選的,是一個可設定的外掛,在預設情況下,SessionFactory不會啓用這個外掛。
什麼樣的數據適合存放到第二級快取中?
很少被修改的數據
不是很重要的數據,允許出現偶爾併發的數據
不會被併發存取的數據
常數數據
不適合存放到第二級快取的數據?
經常被修改的數據
絕對不允許出現併發存取的數據,如財務數據,絕對不允許出現併發
與其他應用共用的數據。
擴充套件:Hibernate的二級快取預設是不支援分佈式快取的。使用 memcahe,redis等中央快取來代替二級快取。
Hibernate查詢物件如何應用快取?
當Hibernate根據ID存取數據物件的時候,首先從Session一級快取中查;查不到,如果設定了二級快取,那麼從二級快取中查;如果都查不到,再查詢數據庫,把結果按照ID放入到快取。
刪除、更新、增加數據的時候,同時更新快取
Hibernate管理快取範例
無論何時,我們在管理Hibernate快取(Managing the caches)時,當你給save()、update()或saveOrUpdate()方法傳遞一個物件時,或使用load()、 get()、list()、iterate() 或scroll()方法獲得一個物件時, 該物件都將被加入到Session的內部快取中。
當隨後flush()方法被呼叫時,物件的狀態會和數據庫取得同步。 如果你不希望此同步操作發生,或者你正處理大量物件、需要對有效管理記憶體時,你可以呼叫evict() 方法,從一級快取中去掉這些物件及其集合。
第二種回答:
hibernate 常用的快取有一級快取和二級快取:
一級快取:也叫 Session 快取,只在 Session 作用範圍內有效,不需要使用者幹涉,由 hibernate 自身維護,可以通過:evict(object)清除 object 的快取;clear()清除一級快取中的所有快取;flush()刷出快取;
二級快取:應用級別的快取,在所有 Session 中都有效,支援設定第三方的快取,如:EhCache。
hibernate裡物件有三種狀態:
Transient(瞬時):物件剛new出來,還沒設id,設了其他值。
Persistent(持久):呼叫了save()、saveOrUpdate(),就變成Persistent,有id。
etached(脫管):當session close()完之後,變成Detached。
在這裏插入圖片描述
第二種回答:
臨時/瞬時狀態:直接 new 出來的物件,該物件還沒被持久化(沒儲存在數據庫中),不受 Session 管理。
持久化狀態:當呼叫 Session 的 save/saveOrupdate/get/load/list 等方法的時候,物件就是持久化狀態。
遊離狀態:Session 關閉之後物件就是遊離狀態。
openSession 從字面上可以看得出來,是開啓一個新的session物件,而且每次使用都是開啓一個新的session,假如連續使用多次,則獲得的session不是同一個物件,並且使用完需要呼叫close方法關閉session。
getCurrentSession ,從字面上可以看得出來,是獲取當前上下文一個session物件,當第一次使用此方法時,會自動產生一個session物件,並且連續使用多次時,得到的session都是同一個物件,這就是與openSession的區別之一,簡單而言,getCurrentSession 就是:如果有已經使用的,用舊的,如果沒有,建新的。
注意:在實際開發中,往往使用getCurrentSession多,因爲一般是處理同一個事務(即是使用一個數據庫的情況),所以在一般情況下比較少使用openSession或者說openSession是比較老舊的一套介面了。
第二種回答:
getCurrentSession 會系結當前執行緒,而 openSession 則不會。
getCurrentSession 事務是 Spring 控制的,並且不需要手動關閉,而 openSession 需要我們自己手動開啓和提交事務。
必須,因爲hibernate框架會呼叫這個預設構造方法來構造範例物件,即Class類的newInstance方法,這個方法就是通過呼叫預設構造方法來建立範例物件的。
另外再提醒一點,如果你沒有提供任何構造方法,虛擬機器會自動提供預設構造方法(無參構造器),但是如果你提供了其他有參數的構造方法的話,虛擬機器就不再爲你提供預設構造方法,這時必須手動把無參構造器寫在程式碼裡,否則new Xxxx()是會報錯的,所以預設的構造方法不是必須的,只在有多個構造方法時纔是必須的,這裏「必須」指的是「必須手動寫出來」。
第二種回答:
hibernate 中每個實體類必須提供一個無參建構函式,因爲 hibernate 框架要使用 reflection api,通過呼叫 ClassnewInstance() 來建立實體類的範例,如果沒有無參的建構函式就會拋出異常。
#{}是預編譯處理,KaTeX parse error: Expected 'EOF', got '#' at position 21: …串替換。
Mybatis在處理#̲{}時,會將sql中的#{}替…{}時,就是把${}替換成變數的值。
使用#{}可以有效的防止SQL隱碼攻擊,提高系統安全性。
陣列分頁
sql分頁
攔截器分頁
RowBounds分頁
第二種回答:
分頁方式:邏輯分頁和物理分頁。
邏輯分頁: 使用 MyBatis 自帶的 RowBounds 進行分頁,它是一次性查詢很多數據,然後在數據中再進行檢索。
物理分頁: 自己手寫 SQL 分頁或使用分頁外掛 PageHelper,去數據庫查詢指定條數的分頁數據的形式。
RowBounds 表面是在「所有」數據中檢索數據,其實並非是一次性查詢出所有數據,因爲 MyBatis 是對 jdbc 的封裝,在 jdbc 驅動中有一個 Fetch Size 的設定,它規定了每次最多從數據庫查詢多少條數據,假如你要查詢更多數據,它會在你執行 next()的時候,去查詢更多的數據。就好比你去自動取款機取 10000 元,但取款機每次最多能取 2500 元,所以你要取 4 次才能 纔能把錢取完。只是對於 jdbc 來說,當你呼叫 next()的時候會自動幫你完成查詢工作。這樣做的好處可以有效的防止記憶體溢位。
物理分頁速度上並不一定快於邏輯分頁,邏輯分頁速度上也並不一定快於物理分頁。
物理分頁總是優於邏輯分頁:沒有必要將屬於數據庫端的壓力加諸到應用端來,就算速度上存在優勢,然而其它效能上的優點足以彌補這個缺點。
第二種回答:
邏輯分頁是一次性查詢很多數據,然後再在結果中檢索分頁的數據。這樣做弊端是需要消耗大量的記憶體、有記憶體溢位的風險、對數據庫壓力較大。
物理分頁是從數據庫查詢指定條數的數據,彌補了一次性全部查出的所有數據的種種缺點,比如需要大量的記憶體,對數據庫查詢壓力較大等問題。
Mybatis僅支援association關聯物件和collection關聯集合物件的延遲載入,association指的就是一對一,collection指的就是一對多查詢。在Mybatis組態檔中,可以設定是否啓用延遲載入lazyLoadingEnabled=true|false。
它的原理是,使用CGLIB建立目標物件的代理物件,當呼叫目標方法時,進入攔截器方法,比如呼叫a.getB().getName(),攔截器invoke()方法發現a.getB()是null值,那麼就會單獨發送事先儲存好的查詢關聯B物件的sql,把B查詢上來,然後呼叫a.setB(b),於是a的物件b屬性就有值了,接着完成a.getB().getName()方法的呼叫。這就是延遲載入的基本原理。
當然了,不光是Mybatis,幾乎所有的包括Hibernate,支援延遲載入的原理都是一樣的。
第二種回答:
MyBatis 支援延遲載入,設定 lazyLoadingEnabled=true 即可。
延遲載入的原理的是呼叫的時候觸發載入,而不是在初始化的時候就載入資訊。比如呼叫 a. getB(). getName(),這個時候發現 a. getB() 的值爲 null,此時會單獨觸發事先儲存好的關聯 B 物件的 SQL,先查詢出來 B,然後再呼叫 a. setB(b),而這時候再呼叫 a. getB(). getName() 就有值了,這就是延遲載入的基本原理。
一級快取: 基於 PerpetualCache 的 HashMap 本地快取,其儲存作用域爲 Session,當 Session flush 或 close 之後,該 Session 中的所有 Cache 就將清空,預設開啓一級快取。
二級快取與一級快取其機制 機製相同,預設也是採用 PerpetualCache,HashMap 儲存,不同在於其儲存作用域爲 Mapper(Namespace),並且可自定義儲存源,如 Ehcache。預設不開啓二級快取,要開啓二級快取,使用二級快取屬性類需要實現Serializable序列化介面(可用來儲存物件的狀態),可在它的對映檔案中設定 ;
對於快取數據更新機制 機製,當某一個作用域(一級快取 Session/二級快取Namespaces)的進行了C/U/D 操作後,預設該作用域下所有 select 中的快取將被 clear。
第二種回答:
一級快取:基於 PerpetualCache 的 HashMap 本地快取,它的宣告週期是和 SQLSession 一致的,有多個 SQLSession 或者分佈式的環境中數據庫操作,可能會出現髒數據。當 Session flush 或 close 之後,該 Session 中的所有 Cache 就將清空,預設一級快取是開啓的。
二級快取:也是基於 PerpetualCache 的 HashMap 本地快取,不同在於其儲存作用域爲 Mapper 級別的,如果多個SQLSession之間需要共用快取,則需要使用到二級快取,並且二級快取可自定義儲存源,如 Ehcache。預設不開啓二級快取,要開啓二級快取,使用二級快取屬性類需要實現 Serializable 序列化介面(可用來儲存物件的狀態)。
開啓二級快取數據查詢流程:二級快取 -> 一級快取 -> 數據庫。
快取更新機制 機製:當某一個作用域(一級快取 Session/二級快取 Mapper)進行了C/U/D 操作後,預設該作用域下所有 select 中的快取將被 clear。
(1)Mybatis和hibernate不同,它不完全是一個ORM框架,因爲MyBatis需要程式設計師自己編寫Sql語句。
(2)Mybatis直接編寫原生態sql,可以嚴格控制sql執行效能,靈活度高,非常適合對關係數據模型要求不高的軟件開發,因爲這類軟體需求變化頻繁,一但需求變化要求迅速輸出成果。但是靈活的前提是mybatis無法做到數據庫無關性,如果需要實現支援多種數據庫的軟體,則需要自定義多套sql對映檔案,工作量大。
(3)Hibernate物件/關係對映能力強,數據庫無關性好,對於關係模型要求高的軟體,如果用hibernate開發可以節省很多程式碼,提高效率。
第二種回答:
靈活性:MyBatis 更加靈活,自己可以寫 SQL 語句,使用起來比較方便。
可移植性:MyBatis 有很多自己寫的 SQL,因爲每個數據庫的 SQL 可以不相同,所以可移植性比較差。
學習和使用門檻:MyBatis 入門比較簡單,使用門檻也更低。
二級快取:hibernate 擁有更好的二級快取,它的二級快取可以自行更換爲第三方的二級快取。
Mybatis有三種基本的執行器(Executor):
SimpleExecutor:每執行一次update或select,就開啓一個Statement物件,用完立刻關閉Statement物件。
ReuseExecutor:執行update或select,以sql作爲key查詢Statement物件,存在就使用,不存在就建立,用完後,不關閉Statement物件,而是放置於Map內,供下一次使用。簡言之,就是重複使用Statement物件。
BatchExecutor:執行update(沒有select,JDBC批次處理不支援select),將所有sql都新增到批次處理中(addBatch()),等待統一執行(executeBatch()),它快取了多個Statement物件,每個Statement物件都是addBatch()完畢後,等待逐一執行executeBatch()批次處理。與JDBC批次處理相同。
Mybatis使用RowBounds物件進行分頁,它是針對ResultSet結果集執行的記憶體分頁,而非物理分頁。可以在sql內直接書寫帶有物理分頁的參數來完成物理分頁功能,也可以使用分頁外掛來完成物理分頁。
分頁外掛的基本原理是使用Mybatis提供的外掛介面,實現自定義外掛,在外掛的攔截方法內攔截待執行的sql,然後重寫sql,根據dialect方言,新增對應的物理分頁語句和物理分頁參數。
Mybatis僅可以編寫針對ParameterHandler、ResultSetHandler、StatementHandler、Executor這4種介面的外掛,Mybatis使用JDK的動態代理,爲需要攔截的介面生成代理物件以實現介面方法攔截功能,每當執行這4種介面物件的方法時,就會進入攔截方法,具體就是InvocationHandler的invoke()方法,當然,只會攔截那些你指定需要攔截的方法。
具體攔截方式爲:
(1)Executor:攔截內部執行器的方法(log記錄),它負責呼叫 StatementHandler 操作數據庫,並把結果集通過 ResultSetHandler 進行自動對映,另外它還處理了二級快取的操作;
(2)StatementHandler :攔截Sql語法構建的處理,它是 MyBatis 直接和數據庫執行 SQL 指令碼的物件,另外它也實現了 MyBatis 的一級快取;
(3)ParameterHandler :攔截參數的處理
(4)ResultSetHandler :攔截結果集的處理
編寫外掛:實現Mybatis的Interceptor介面並複寫intercept()方法,然後在給外掛編寫註解,指定要攔截哪一個介面的哪些方法即可,記住,別忘了在組態檔中設定你編寫的外掛。
自定義外掛實現原理
MyBatis 外掛要實現 Interceptor 介面,介面包含的方法,如下:
public interface Interceptor {
Object intercept(Invocation invocation) throws Throwable;
Object plugin(Object target);
void setProperties(Properties properties);
}
intercept 方法就是要進行攔截的時候要執行的方法。
plugin 方法是外掛用於封裝目標物件的,通過該方法我們可以返回目標物件本身,也可以返回一個它的代理,可以決定是否要進行攔截進而決定要返回一個什麼樣的目標物件,官方提供了範例:return Plugin. wrap(target, this);
setProperties 方法是在 MyBatis 進行設定外掛的時候可以設定自定義相關屬性,即:介面實現物件的參數設定;
自定義外掛實現範例
官方外掛實現:
@Intercepts({@Signature(type = Executor. class, method = "query",
args = {MappedStatement. class, Object. class, RowBounds. class, ResultHandler. class})})
public class TestInterceptor implements Interceptor {
public Object intercept(Invocation invocation) throws Throwable {
Object target = invocation. getTarget(); //被代理物件
Method method = invocation. getMethod(); //代理方法
Object[] args = invocation. getArgs(); //方法參數
// do something . . . . . . 方法攔截前執行程式碼塊
Object result = invocation. proceed();
// do something . . . . . . . 方法攔截後執行程式碼塊
return result;
}
public Object plugin(Object target) {
return Plugin. wrap(target, this);
}
}
一個@Intercepts可以設定多個@Signature,@Signature中的參數定義如下:
type:表示攔截的類,這裏是Executor的實現類;
method:表示攔截的方法,這裏是攔截Executor的update方法;
args:表示方法參數。
①. 跨系統的非同步通訊,所有需要非同步互動的地方都可以使用訊息佇列。就像我們除了打電話(同步)以外,還需要發簡訊,發電子郵件(非同步)的通訊方式。
②. 多個應用之間的耦合,由於訊息是平臺無關和語言無關的,而且語意上也不再是函數呼叫,因此更適合作爲多個應用之間的松耦合的介面。基於訊息佇列的耦合,不需要發送方和接收方同時線上。在企業應用整合(EAI)中,檔案傳輸,共用數據庫,訊息佇列,遠端過程呼叫都可以作爲整合的方法。
③. 應用內的同步變非同步,比如訂單處理,就可以由前端應用將訂單資訊放到佇列,後端應用從佇列裡依次獲得訊息處理,高峯時的大量訂單可以積壓在佇列裡慢慢處理掉。由於同步通常意味着阻塞,而大量執行緒的阻塞會降低計算機的效能。
④. 訊息驅動的架構(EDA),系統分解爲訊息佇列,和訊息製造者和訊息消費者,一個處理流程可以根據需要拆成多個階段(Stage),階段之間用佇列連線起來,前一個階段處理的結果放入佇列,後一個階段從佇列中獲取訊息繼續處理。
⑤. 應用需要更靈活的耦合方式,如發佈訂閱,比如可以指定路由規則。
⑥. 跨區域網,甚至跨城市的通訊(CDN行業),比如北京機房與廣州機房的應用程式的通訊。
第二種回答:
搶購活動,削峯填谷,防止系統崩塌。
延遲資訊處理,比如 10 分鐘之後給下單未付款的使用者發送郵件提醒。
解耦系統,對於新增的功能可以單獨寫模組擴充套件,比如使用者確認評價之後,新增了給使用者返積分的功能,這個時候不用在業務程式碼裡新增新增積分的功能,只需要把新增積分的介面訂閱確認評價的訊息佇列即可,後面再新增任何功能只需要訂閱對應的訊息佇列即可。
RabbitMQ 中重要的角色有:生產者、消費者和代理:
生產者:訊息的建立者,負責建立和推播數據到訊息伺服器;
消費者:訊息的接收方,用於處理數據和確認訊息;
代理:就是 RabbitMQ 本身,用於扮演「快遞」的角色,本身不生產訊息,只是扮演「快遞」的角色。
ConnectionFactory(連線管理器):應用程式與Rabbit之間建立連線的管理器,程式程式碼中使用。
Channel(通道):訊息推播使用的通道。
Exchange(交換器):用於接受、分配訊息。
Queue(佇列):用於儲存生產者的訊息。
RoutingKey(路由鍵):用於把生成者的數據分配到交換器上。
BindingKey(系結鍵):用於把交換器的訊息系結到佇列上。
vhost 可以理解爲虛擬 broker ,即 mini-RabbitMQ server。其內部均含有獨立的 queue、exchange 和 binding 等,但最最重要的是,其擁有獨立的許可權系統,可以做到 vhost 範圍的使用者控制。當然,從 RabbitMQ 的全域性角度,vhost 可以作爲不同許可權隔離的手段(一個典型的例子就是不同的應用可以跑在不同的 vhost 中)。
第二種回答:
vhost:每個 RabbitMQ 都能建立很多 vhost,我們稱之爲虛擬主機,每個虛擬主機其實都是 mini 版的RabbitMQ,它擁有自己的佇列,交換器和系結,擁有自己的許可權機制 機製。
首先用戶端必須連線到 RabbitMQ 伺服器才能 纔能發佈和消費訊息,用戶端和 rabbit server 之間會建立一個 tcp 連線,一旦 tcp 開啓並通過了認證(認證就是你發送給 rabbit 伺服器的使用者名稱和密碼),你的用戶端和 RabbitMQ 就建立了一條 amqp 通道(channel),通道是建立在「真實」 tcp 上的虛擬連線,amqp 命令都是通過通道發送出去的,每個通道都會有一個唯一的 id,不論是發佈訊息,訂閱佇列都是通過這個通道完成的。
提供了事務的功能。
通過將 channel 設定爲 confirm(確認)模式。
訊息持久化
ACK確認機制 機製
設定叢集映象模式
訊息補償機制 機製
第二種回答:
把訊息持久化磁碟,保證伺服器重新啓動訊息不丟失。
每個叢集中至少有一個物理磁碟,保證訊息落入磁碟。
宣告佇列必須設定持久化 durable 設定爲 true.
訊息推播投遞模式必須設定持久化,deliveryMode 設定爲 2(持久)。
訊息已經到達持久化交換器。
訊息已經到達持久化佇列。
以上四個條件都滿足才能 纔能保證訊息持久化成功。
持久化的缺地就是降低了伺服器的吞吐量,因爲使用的是磁碟而非記憶體儲存,從而降低了吞吐量。可儘量使用 ssd 硬碟來緩解吞吐量的問題。
三種廣播模式:
fanout: 所有bind到此exchange的queue都可以接收訊息(純廣播,系結到RabbitMQ的接受者都能收到訊息);
direct: 通過routingKey和exchange決定的那個唯一的queue可以接收訊息;
topic:所有符合routingKey(此時可以是一個表達式)的routingKey所bind的queue可以接收訊息;
第二種回答:
direct(預設方式):最基礎最簡單的模式,發送方把訊息發送給訂閱方,如果有多個訂閱者,預設採取輪詢的方式進行訊息發送。
headers:與 direct 類似,只是效能很差,此型別幾乎用不到。
fanout:分發模式,把消費分發給所有訂閱者。
topic:匹配訂閱模式,使用正則匹配到訊息佇列,能匹配到的都能接收到。
具體詳情參考此文:
通過訊息過期後進入死信交換器,再由交換器轉發到延遲消費佇列,實現延遲功能;
使用 RabbitMQ-delayed-message-exchange 外掛實現延遲功能。
叢集主要有以下兩個用途:
高可用:某個伺服器出現問題,整個 RabbitMQ 還可以繼續使用;
高容量:叢集可以承載更多的訊息量。
磁碟節點:訊息會儲存到磁碟。
記憶體節點:訊息都儲存在記憶體中,重新啓動伺服器訊息丟失,效能高於磁碟型別。
各節點之間使用「–link」連線,此屬性不能忽略。
各節點使用的 erlang cookie 值必須相同,此值相當於「祕鑰」的功能,用於各節點的認證。
整個叢集中必須包含一個磁碟節點。
不是,原因有以下兩個:
儲存空間的考慮:如果每個節點都擁有所有佇列的完全拷貝,這樣新增節點不但沒有新增儲存空間,反而增加了更多的冗餘數據;
效能的考慮:如果每條訊息都需要完整拷貝到每一個叢集節點,那新增節點並沒有提升處理訊息的能力,最多是保持和單節點相同的效能甚至是更糟。
如果唯一磁碟的磁碟節點崩潰了,不能進行以下操作:
不能建立佇列
不能建立交換器
不能建立系結
不能新增使用者
不能更改許可權
不能新增和刪除叢集節點
唯一磁碟節點崩潰了,叢集是可以保持執行的,但你不能更改任何東西。
RabbitMQ 對叢集的停止的順序是有要求的,應該先關閉記憶體節點,最後再關閉磁碟節點。如果順序恰好相反的話,可能會造成訊息的丟失。
kafka 不能脫離 zookeeper 單獨使用,因爲 kafka 使用 zookeeper 管理和協調 kafka 的節點伺服器。
kafka 有兩種數據儲存策略:
按照過期時間保留;
按照儲存的訊息大小保留。
這個時候 kafka 會執行數據清除工作,時間和大小不論那個滿足條件,都會清空數據。
cpu 效能瓶頸
磁碟讀寫瓶頸
網路瓶頸
叢集的數量不是越多越好,最好不要超過 7 個,因爲節點越多,訊息複製需要的時間就越長,整個羣組的吞吐量就越低。
叢集數量最好是單數,因爲超過一半故障叢集就不能用了,設定爲單數容錯率更高。
zookeeper 是一個分佈式的,開放原始碼的分佈式應用程式協調服務,是 google chubby 的開源實現,是 hadoop 和 hbase 的重要元件。它是一個爲分佈式應用提供一致性服務的軟體,提供的功能包括:設定維護、域名服務、分佈式同步、組服務等。
叢集管理:監控節點存活狀態、執行請求等。
主節點選舉:主節點掛掉了之後可以從備用的節點開始新一輪選主,主節點選舉說的就是這個選舉的過程,使用 zookeeper 可以協助完成這個過程。
分佈式鎖:zookeeper 提供兩種鎖:獨佔鎖、共用鎖。獨佔鎖即一次只能有一個執行緒使用資源,共用鎖是讀鎖共用,讀寫互斥,即可以有多線執行緒同時讀同一個資源,如果要使用寫鎖也只能有一個執行緒使用。zookeeper可以對分佈式鎖進行控制。
命名服務:在分佈式系統中,通過使用命名服務,用戶端應用能夠根據指定名字來獲取資源或服務的地址,提供者等資訊。
zookeeper 有三種部署模式:
單機部署:一臺叢集上執行;
叢集部署:多臺叢集執行;
僞叢集部署:一臺叢集啓動多個 zookeeper 範例執行。
zookeeper 的核心是原子廣播,這個機制 機製保證了各個 server 之間的同步。實現這個機制 機製的協定叫做 zab 協定。 zab 協定有兩種模式,分別是恢復模式(選主)和廣播模式(同步)。當服務啓動或者在領導者崩潰後,zab 就進入了恢復模式,當領導者被選舉出來,且大多數 server 完成了和 leader 的狀態同步以後,恢復模式就結束了。狀態同步保證了 leader 和 server 具有相同的系統狀態。
在分佈式環境中,有些業務邏輯只需要叢集中的某一臺機器進行執行,其他的機器可以共用這個結果,這樣可以大大減少重複計算,提高效能,所以就需要主節點。
可以繼續使用,單數伺服器只要沒超過一半的伺服器宕機就可以繼續使用。
用戶端端會對某個 znode 建立一個 watcher 事件,當該 znode 發生變化時,這些用戶端會收到 zookeeper 的通知,然後用戶端可以根據 znode 變化來做出業務上的改變。
第一範式:強調的是列的原子性,即數據庫表的每一列都是不可分割的原子數據項。
第二範式:要求實體的屬性完全依賴於主關鍵字。所謂完全依賴是指不能存在僅依賴主關鍵字一部分的屬性。
第三範式:任何非主屬性不依賴於其它非主屬性。
表型別如果是 MyISAM ,那 id 就是 8。
表型別如果是 InnoDB,那 id 就是 6。
InnoDB 表只會把自增主鍵的最大 id 記錄在記憶體中,所以重新啓動之後會導致最大 id 丟失。
使用 select version() 獲取當前 MySQL 數據庫版本。
Atomicity(原子性):一個事務(transaction)中的所有操作,或者全部完成,或者全部不完成,不會結束在中間某個環節。事務在執行過程中發生錯誤,會被恢復(Rollback)到事務開始前的狀態,就像這個事務從來沒有執行過一樣。即,事務不可分割、不可約簡。
Consistency(一致性):在事務開始之前和事務結束以後,數據庫的完整性沒有被破壞。這表示寫入的資料必須完全符合所有的預設約束、觸發器、級聯回滾等。
Isolation(隔離性):數據庫允許多個併發事務同時對其數據進行讀寫和修改的能力,隔離性可以防止多個事務併發執行時由於交叉執行而導致數據的不一致。事務隔離分爲不同級別,包括讀未提交(Read uncommitted)、讀提交(read committed)、可重複讀(repeatable read)和序列化(Serializable)。
Durability(永續性):事務處理結束後,對數據的修改就是永久的,即便系統故障也不會丟失。
char(n) :固定長度型別,比如訂閱 char(10),當你輸入"abc"三個字元的時候,它們佔的空間還是 10 個位元組,其他 7 個是空位元組。
chat 優點:
效率高;缺點:佔用空間;適用場景:儲存密碼的 md5 值,固定長度的,使用 char 非常合適。
varchar(n) :可變長度,儲存的值是每個值佔用的位元組再加上一個用來記錄其長度的位元組的長度。
所以,從空間上考慮 varcahr 比較合適;從效率上考慮 char 比較合適,二者使用需要權衡。
float 最多可以儲存 8 位的十進制數,並在記憶體中佔 4 位元組。
double 最可可以儲存 16 位的十進制數,並在記憶體中佔 8 位元組。
內連線關鍵字:inner join;
左連線:left join;
右連線:right join。
內連線是把匹配的關聯數據顯示出來;左連線是左邊的表全部顯示出來,右邊的表顯示出符合條件的數據;右連線正好相反。
索引是滿足某種特定查詢演算法的數據結構,而這些數據結構會以某種方式指向數據,從而實現高效查詢數據。
具體來說 MySQL 中的索引,不同的數據引擎實現有所不同,但目前主流的數據庫引擎的索引都是 B+ 樹實現的,B+ 樹的搜尋效率,可以到達二分法的效能,找到數據區域之後就找到了完整的數據結構了,所有索引的效能也是更好的。
使用 explain 檢視 SQL 是如何執行查詢語句的,從而分析你的索引是否滿足需求。
explain 語法:explain select * from table where type=1。
MySQL 的事務隔離是在 MySQL. ini 組態檔裡新增的,在檔案的最後新增:transaction-isolation = REPEATABLE-READ
可用的設定值:READ-UNCOMMITTED、READ-COMMITTED、REPEATABLE-READ、SERIALIZABLE。
READ-UNCOMMITTED:未提交讀,最低隔離級別、事務未提交前,就可被其他事務讀取(會出現幻讀、髒讀、不可重複讀)。
READ-COMMITTED:提交讀,一個事務提交後才能 纔能被其他事務讀取到(會造成幻讀、不可重複讀)。
REPEATABLE-READ:可重複讀,預設級別,保證多次讀取同一個數據時,其值都和事務開始時候的內容是一致,禁止讀取到別的事務未提交的數據(會造成幻讀)。
SERIALIZABLE:序列化,代價最高最可靠的隔離級別,該隔離級別能防止髒讀、不可重複讀、幻讀。
(1)髒讀 :表示一個事務能夠讀取另一個事務中還未提交的數據。比如,某個事務嘗試插入記錄 A,此時該事務還未提交,然後另一個事務嘗試讀取到了記錄 A。
(2)不可重複讀 :是指在一個事務內,多次讀同一數據。
(3)幻讀 :指同一個事務內多次查詢返回的結果集不一樣。比如同一個事務 A 第一次查詢時候有 n 條記錄,但是第二次同等條件下查詢卻有 n+1 條記錄,這就好像產生了幻覺。發生幻讀的原因也是另外一個事務新增或者刪除或者修改了第一個事務結果集裏面的數據,同一個記錄的數據內容被修改了,所有數據行的記錄就變多或者變少了。
MyIASM 引擎:
MySQL 的預設引擎,但不提供事務的支援,也不支援行級鎖和外來鍵。因此當執行插入和更新語句時,即執行寫操作的時候需要鎖定這個表,所以會導致效率會降低。不過和 InnoDB 不同的是,MyIASM 引擎是儲存了表的行數,於是當進行 select count(*) from table 語句時,可以直接的讀取已經儲存的值而不需要進行掃描全表。所以,如果表的讀操作遠遠多於寫操作時,並且不需要事務的支援的,可以將 MyIASM 作爲數據庫引擎的首選。
InnoDB 引擎:
InnoDB 引擎提供了對數據庫 acid 事務的支援,並且還提供了行級鎖和外來鍵的約束,它的設計的目標就是處理大數據容量的數據庫系統。MySQL 執行的時候,InnoDB 會在記憶體中建立緩衝池,用於緩衝數據和索引。但是該引擎是不支援全文搜尋,同時啓動也比較的慢,它是不會儲存表的行數的,所以當進行 select count(*) from table 指令的時候,需要進行掃描全表。由於鎖的粒度小,寫操作是不會鎖定全表的,所以在併發度較高的場景下使用會提升效率的。
補充:
MySQL在V5.1之前預設儲存引擎是MyISAM;在此之後(V5.5)預設儲存引擎是InnoDB
MyISAM 只支援表鎖,InnoDB 支援表鎖和行鎖,預設爲行鎖。
表級鎖:開銷小,加鎖快,不會出現死鎖。鎖定粒度大,發生鎖衝突的概率最高,併發量最低。
行級鎖:開銷大,加鎖慢,會出現死鎖。鎖力度小,發生鎖衝突的概率小,併發度最高。
樂觀鎖:每次去拿數據的時候都認爲別人不會修改,所以不會上鎖,但是在提交更新的時候會判斷一下在此期間別人有沒有去更新這個數據。
悲觀鎖:每次去拿數據的時候都認爲別人會修改,所以每次在拿數據的時候都會上鎖,這樣別人想拿這個數據就會阻止,直到這個鎖被釋放。
數據庫的樂觀鎖需要自己實現,在表裏面新增一個 version 欄位,每次修改成功值加 1,這樣每次修改的時候先對比一下,自己擁有的 version 和數據庫現在的 version 是否一致,如果不一致就不修改,這樣就實現了樂觀鎖。
使用 show processlist 命令檢視當前所有連線資訊。
使用 explain 命令查詢 SQL 語句執行計劃。
開啓慢查詢日誌,檢視慢查詢的 SQL。
爲搜尋欄位建立索引。
避免使用 select *,列出需要查詢的欄位。
垂直分割分表。
選擇正確的儲存引擎。
Redis是一個開源的使用ANSI C語言編寫、支援網路、可基於記憶體亦可持久化的日誌型、Key-Value數據庫,並提供多種語言的API。
Redis 使用場景:
數據高併發的讀寫
海量數據的讀寫
對擴充套件性要求高的數據
第二種回答:
Redis 是一個使用 C 語言開發的快取記憶體數據庫。
Redis 使用場景:
記錄貼文點贊數、點選數、評論數;
快取近期熱帖;
快取文章詳情資訊;
記錄使用者對談資訊。
數據快取功能
分佈式鎖的功能
支援數據持久化
支援事務
支援訊息佇列
memcached所有的值均是簡單的字串,redis作爲其替代者,支援更爲豐富的數據型別
redis的速度比memcached快很多
redis可以持久化其數據
第二種回答:
儲存方式不同:memcache 把數據全部存在記憶體之中,斷電後會掛掉,數據不能超過記憶體大小;Redis 有部份存在硬碟上,這樣能保證數據的永續性。
數據支援型別:memcache 對數據型別支援相對簡單;Redis 有複雜的數據型別。
使用底層模型不同:它們之間底層實現方式,以及與用戶端之間通訊的應用協定不一樣,Redis 自己構建了 vm 機制 機製,因爲一般的系統呼叫系統函數的話,會浪費一定的時間去移動和請求。
value 值大小不同:Redis 最大可以達到 1gb;memcache 只有 1mb。
因爲 cpu 不是 Redis 的瓶頸,Redis 的瓶頸最有可能是機器記憶體或者網路頻寬。既然單執行緒容易實現,而且 cpu 又不會成爲瓶頸,那就順理成章地採用單執行緒的方案了。
關於 Redis 的效能,官方網站也有,普通筆電輕鬆處理每秒幾十萬的請求。
而且單執行緒並不代表就慢 nginx 和 nodejs 也都是高效能單執行緒的代表。
快取穿透:指查詢一個一定不存在的數據,由於快取是不命中時需要從數據庫查詢,查不到數據則不寫入快取,這將導致這個不存在的數據每次請求都要到數據庫去查詢,造成快取穿透。
解決方案:最簡單粗暴的方法如果一個查詢返回的數據爲空(不管是數據不存在,還是系統故障),我們就把這個空結果進行快取,但它的過期時間會很短,最長不超過五分鐘。
string、list、hash、set、zset。
第二種回答:
Redis 支援的數據型別:string(字串)、list(列表)、hash(字典)、set(集合)、zset(有序集合)。
Redisson、Jedis、lettuce等等,官方推薦使用Redisson。
Jedis是Redis的Java實現的用戶端,其API提供了比較全面的Redis命令的支援。
Redisson實現了分佈式和可延伸的Java數據結構,和Jedis相比,功能較爲簡單,不支援字串操作,不支援排序、事務、管道、分割區等Redis特性。Redisson的宗旨是促進使用者對Redis的關注分離,從而讓使用者能夠將精力更集中地放在處理業務邏輯上。
合理設定快取的過期時間。
新增、更改、刪除數據庫操作時同步更新 Redis,可以使用事物機制 機製來保證數據的一致性。
188.redis 持久化有幾種方式?
Redis 的持久化有兩種方式,或者說有兩種策略:
RDB(Redis Database):指定的時間間隔能對你的數據進行快照儲存。
AOF(Append Only File):每一個收到的寫命令都通過write函數追加到檔案中。
Redis 分佈式鎖其實就是在系統裏面佔一個「坑」,其他程式也要佔「坑」的時候,佔用成功了就可以繼續執行,失敗了就只能放棄或稍後重試。
佔坑一般使用 setnx(set if not exists)指令,只允許被一個程式佔有,使用完呼叫 del 釋放鎖。
Redis 分佈式鎖不能解決超時的問題,分佈式鎖有一個超時時間,程式的執行如果超出了鎖的超時時間就會出現問題。
補充:
鎖時間預設30S, 有個watch dog自動延期機制 機製, 如果超過30S程式還希望持有鎖, 會自動延長鎖時間
儘可能使用雜湊表(hashes),雜湊表(是說雜湊表裏面儲存的數少)使用的記憶體非常小,所以你應該儘可能的將你的數據模型抽象到一個雜湊表裏面。
比如你的web系統中有一個使用者物件,不要爲這個使用者的名稱,姓氏,郵箱,密碼設定單獨的key,而是應該把這個使用者的所有資訊儲存到一張雜湊表裏面。
第二種回答:
儘量使用 Redis 的雜湊表,把相關的資訊放到雜湊表裏面儲存,而不是把每個欄位單獨儲存,這樣可以有效的減少記憶體使用。
比如將 Web 系統的使用者物件,應該放到雜湊表裏面再整體儲存到 Redis,而不是把使用者的姓名、年齡、密碼、郵箱等欄位分別設定 key 進行儲存。
volatile-lru:從已設定過期時間的數據集(server. db[i]. expires)中挑選最近最少使用的數據淘汰。
volatile-ttl:從已設定過期時間的數據集(server. db[i]. expires)中挑選將要過期的數據淘汰。
volatile-random:從已設定過期時間的數據集(server. db[i]. expires)中任意選擇數據淘汰。
allkeys-lru:從數據集(server. db[i]. dict)中挑選最近最少使用的數據淘汰。
allkeys-random:從數據集(server. db[i]. dict)中任意選擇數據淘汰。
no-enviction(驅逐):禁止驅逐數據。
主伺服器寫記憶體快照,會阻塞主執行緒的工作,當快照比較大時對效能影響是非常大的,會間斷性暫停服務,所以主伺服器最好不要寫記憶體快照。
Redis 主從複製的效能問題,爲了主從複製的速度和連線的穩定性,主從庫最好在同一個區域網內。
類載入器(ClassLoader)
執行時數據區(Runtime Data Area)
執行引擎(Execution Engine)
本地庫介面(Native Interface)
元件的作用:
首先通過javac編譯器把 Java 程式碼轉換成位元組碼,執行時數據區(Runtime Data Area),類載入器(ClassLoader)再把位元組碼載入到jvm記憶體中,而位元組碼檔案只是 JVM 的一套指令集規範,並不能直接交個底層操作系統去執行,因此需要特定的命令解析器執行引擎(Execution Engine),將位元組碼翻譯成底層系統指令,再交由 CPU 去執行,而這個過程中需要呼叫其他語言的本地庫介面(Native Interface)來實現整個程式的功能。
程式計數器
虛擬機器棧
本地方法棧
堆
方法區
有的區域隨着虛擬機器進程的啓動而存在,有的區域則依賴使用者進程的啓動和結束而建立和銷燬。
第二種回答:
不同虛擬機器的執行時數據區可能略微有所不同,但都會遵從 Java 虛擬機器規範, Java 虛擬機器規範規定的區域分爲以下 5 個部分:
程式計數器(Program Counter Register):當前執行緒所執行的位元組碼的行號指示器,位元組碼解析器的工作是通過改變這個計數器的值,來選取下一條需要執行的位元組碼指令,分支、回圈、跳轉、例外處理、執行緒恢復等基礎功能,都需要依賴這個計數器來完成;
Java 虛擬機器棧(Java Virtual Machine Stacks):用於儲存區域性變數表、運算元棧、動態鏈接、方法出口等資訊;
本地方法棧(Native Method Stack):與虛擬機器棧的作用是一樣的,只不過虛擬機器棧是服務 Java 方法的,而本地方法棧是爲虛擬機器呼叫 Native 方法服務的;
Java 堆(Java Heap):Java 虛擬機器中記憶體最大的一塊,是被所有執行緒共用的,幾乎所有的物件範例都在這裏分配記憶體;
方法區(Methed Area):用於儲存已被虛擬機器載入的類資訊、常數、靜態變數、即時編譯後的程式碼等數據。
棧記憶體儲存的是區域性變數而堆記憶體儲存的是實體;
棧記憶體的更新速度要快於堆記憶體,因爲區域性變數的生命週期很短;
棧記憶體存放的變數生命週期一旦結束就會被釋放,而堆記憶體存放的實體會被垃圾回收機制 機製不定時的回收。
第二種回答:
功能方面:堆是用來存放物件的,棧是用來執行程式的。
共用性:堆是執行緒共用的,棧是執行緒私有的。
空間大小:堆大小遠遠大於棧。
佇列和棧都是被用來預儲存數據的。
佇列允許先進先出檢索元素,但也有例外的情況,Deque 介面允許從兩端檢索元素。
棧和佇列很相似,但它執行對元素進行後進先出進行檢索。
在介紹雙親委派模型之前先說下類載入器。對於任意一個類,都需要由載入它的類載入器和這個類本身一同確立在 JVM 中的唯一性,每一個類載入器,都有一個獨立的類名稱空間。類載入器就是根據指定全限定名稱將 class 檔案載入到 JVM 記憶體,然後再轉化爲 class 物件。
類載入器分類:
啓動類載入器(Bootstrap ClassLoader),是虛擬機器自身的一部分,用來載入Java_HOME/lib/目錄中的,或者被 -Xbootclasspath 參數所指定的路徑中並且被虛擬機器識別的類庫;
其他類載入器:
擴充套件類載入器(Extension ClassLoader):負責載入\lib\ext目錄或Java. ext. dirs系統變數指定的路徑中的所有類庫;
應用程式類載入器(Application ClassLoader):負責載入使用者類路徑(classpath)上的指定類庫,我們可以直接使用這個類載入器。一般情況,如果我們沒有自定義類載入器預設就是用這個載入器。
雙親委派模型:如果一個類載入器收到了類載入的請求,它首先不會自己去載入這個類,而是把這個請求委派給父類別載入器去完成,每一層的類載入器都是如此,這樣所有的載入請求都會被傳送到頂層的啓動類載入器中,只有當父載入無法完成載入請求(它的搜尋範圍中沒找到所需的類)時,子載入器纔會嘗試去載入類。
類載入分爲以下 5 個步驟:
載入:根據查詢路徑找到相應的 class 檔案然後匯入;
檢查:檢查載入的 class 檔案的正確性;
準備:給類中的靜態變數分配記憶體空間;
解析:虛擬機器將常數池中的符號參照替換成直接參照的過程。符號參照就理解爲一個標示,而在直接參照直接指向記憶體中的地址;
初始化:對靜態變數和靜態程式碼塊執行初始化工作。
一般有兩種方法來判斷:
參照計數器:爲每個物件建立一個參照計數,有物件參照時計數器 +1,參照被釋放時計數 -1,當計數器爲 0 時就可以被回收。它有一個缺點不能解決回圈參照的問題;
可達性分析:從 GC Roots 開始向下搜尋,搜尋所走過的路徑稱爲參照鏈。當一個物件到 GC Roots 沒有任何參照鏈相連時,則證明此物件是可以被回收的。
強參照
軟參照
弱參照
虛參照(幽靈參照/幻影參照)
第二種回答:
強參照:發生 gc 的時候不會被回收。
軟參照:有用但不是必須的物件,在發生記憶體溢位之前會被回收。
弱參照:有用但不是必須的物件,在下一次GC時會被回收。
虛參照(幽靈參照/幻影參照):無法通過虛參照獲得物件,用 PhantomReference 實現虛參照,虛參照的用途是在 gc 時返回一個通知。
標記-清除演算法
標記-整理演算法
複製演算法
分代演算法
第二種回答:
標記-清除演算法:標記無用物件,然後進行清除回收。缺點:效率不高,無法清除垃圾碎片。
標記-整理演算法:標記無用物件,讓所有存活的物件都向一端移動,然後直接清除掉端邊界以外的記憶體。
複製演算法:按照容量劃分二個大小相等的記憶體區域,當一塊用完的時候將活着的物件複製到另一塊上,然後再把已使用的記憶體空間一次清理掉。缺點:記憶體使用率不高,只有原來的一半。
分代演算法:根據物件存活週期的不同將記憶體劃分爲幾塊,一般是新生代和老年代,新生代基本採用複製演算法,老年代採用標記整理演算法。
Serial:最早的單執行緒序列垃圾回收器。
Serial Old:Serial 垃圾回收器的老年版本,同樣也是單執行緒的,可以作爲 CMS 垃圾回收器的備選預案。
ParNew:是 Serial 的多執行緒版本。
Parallel:Parallel 和 ParNew 收集器類似是多執行緒的,但 Parallel 是吞吐量優先的收集器,可以犧牲等待時間換取系統的吞吐量。
Parallel Old:Parallel Old 是 Parallel 老生代版本,Parallel 使用的是複製的記憶體回收演算法,Parallel Old 使用的是標記-整理的記憶體回收演算法。
CMS:一種以獲得最短停頓時間爲目標的收集器,非常適用 B/S 系統。
G1:一種兼顧吞吐量和停頓時間的 GC 實現,是 JDK 9 以後的預設 GC 選項。
CMS 是英文 Concurrent Mark-Sweep 的簡稱,是以犧牲吞吐量爲代價來獲得最短回收停頓時間的垃圾回收器。對於要求伺服器響應速度的應用上,這種垃圾回收器非常適合。在啓動 JVM 的參數加上「-XX:+UseConcMarkSweepGC」來指定使用 CMS 垃圾回收器。
CMS 使用的是標記-清除的演算法實現的,所以在 gc 的時候回產生大量的記憶體碎片,當剩餘記憶體不能滿足程式執行要求時,系統將會出現 Concurrent Mode Failure,臨時 CMS 會採用 Serial Old 回收器進行垃圾清除,此時的效能將會被降低。
新生代回收器:Serial、ParNew、Parallel Scavenge
老年代回收器:Serial Old、Parallel Old、CMS
整堆回收器:G1
新生代垃圾回收器一般採用的是複製演算法,複製演算法的優點是效率高,缺點是記憶體利用率低;老年代回收器一般採用的是標記-整理的演算法進行垃圾回收。
分代回收器有兩個分割區:老生代和新生代,新生代預設的空間佔比總空間的 1/3,老生代的預設佔比是 2/3。
新生代使用的是複製演算法,新生代裡有 3 個分割區:Eden、To Survivor、From Survivor,它們的預設佔比是 8:1:1,它的執行流程如下:
把 Eden + From Survivor 存活的物件放入 To Survivor 區;
清空 Eden 和 From Survivor 分割區;
From Survivor 和 To Survivor 分割區交換,From Survivor 變 To Survivor,To Survivor 變 From Survivor。
每次在 From Survivor 到 To Survivor 移動時都存活的物件,年齡就 +1,當年齡到達 15(預設設定是 15)時,升級爲老生代。大物件也會直接進入老生代。
老生代當空間佔用到達某個值之後就會觸發全域性垃圾收回,一般使用標記整理的執行演算法。以上這些循環往復就構成了整個分代垃圾回收的整體執行流程。
JDK 自帶了很多監控工具,都位於 JDK 的 bin 目錄下,其中最常用的是 jconsole 和 jvisualvm 這兩款檢視監控工具。
jconsole:用於對 JVM 中的記憶體、執行緒和類等進行監控;
jvisualvm:JDK 自帶的全能分析工具,可以分析:記憶體快照、執行緒快照、程式死鎖、監控記憶體的變化、gc 變化等。
-Xms2g:初始化推大小爲 2g;
-Xmx2g:堆最大記憶體爲 2g;
-XX:NewRatio=4:設定年輕的和老年代的記憶體比例爲 1:4;
-XX:SurvivorRatio=8:設定新生代 Eden 和 Survivor 比例爲 8:2;
–XX:+UseParNewGC:指定使用 ParNew + Serial Old 垃圾回收器組合;
-XX:+UseParallelOldGC:指定使用 ParNew + ParNew Old 垃圾回收器組合;
-XX:+UseConcMarkSweepGC:指定使用 CMS + Serial Old 垃圾回收器組合;
-XX:+PrintGC:開啓列印 gc 資訊;
-XX:+PrintGCDetails:列印 gc 詳細資訊。